;(function(factory) {
  if (typeof window.define === 'function') {
    if (window.define.amd) {
      // AMD模式
      window.define('wangEditor', ['jquery'], factory)
    } else if (window.define.cmd) {
      // CMD模式
      window.define(function(require, exports, module) {
        return factory
      })
    } else {
      // 全局模式
      factory(window.jQuery)
    }
  } else if (typeof module === 'object' && typeof module.exports === 'object') {
    // commonjs

    // 引用 css —— webapck
    require('../css/wangEditor.css')
    module.exports = factory(
      // 传入 jquery ，支持使用 npm 方式或者自己定义jquery的路径
      require('jquery')
    )
  } else {
    // 全局模式
    factory(window.jQuery)
  }
})(function($) {
  // 验证是否引用jquery
  if (!$ || !$.fn || !$.fn.jquery) {
    alert('在引用wangEditor.js之前，先引用jQuery，否则无法使用 wangEditor')
    return
  }

  // 编辑器（整体）构造函数
  var E = function(elem) {
    // 支持 id 和 element 两种形式
    if (typeof elem === 'string') {
      elem = '#' + elem
    }

    // ---------------获取基本节点------------------
    var $elem = $(elem)
    if ($elem.length !== 1) {
      return
    }
    var nodeName = $elem[0].nodeName
    if (nodeName !== 'TEXTAREA' && nodeName !== 'DIV') {
      // 只能是 textarea 和 div ，其他类型的元素不行
      return
    }
    this.valueNodeName = nodeName.toLowerCase()
    this.$valueContainer = $elem

    // 记录 elem 的 prev 和 parent（最后渲染 editor 要用到）
    this.$prev = $elem.prev()
    this.$parent = $elem.parent()

    // ------------------初始化------------------
    this.init()
  }

  E.fn = E.prototype

  E.$body = $('body')
  E.$document = $(document)
  E.$window = $(window)
  E.userAgent = navigator.userAgent
  E.getComputedStyle = window.getComputedStyle
  E.w3cRange = typeof document.createRange === 'function'
  E.hostname = location.hostname.toLowerCase()
  E.websiteHost = 'wangeditor.github.io|www.wangeditor.com|wangeditor.coding.me'
  E.isOnWebsite = E.websiteHost.indexOf(E.hostname) >= 0
  E.docsite = 'http://www.kancloud.cn/wangfupeng/wangeditor2/113961'

  // 注册 plugin 事件，用于用户自定义插件
  // 用户在引用 wangEditor.js 之后，还可以通过 E.plugin() 注入自定义函数，
  // 该函数将会在 editor.create() 方法的最后一步执行
  E.plugin = function(fn) {
    if (!E._plugins) {
      E._plugins = []
    }

    if (typeof fn === 'function') {
      E._plugins.push(fn)
    }
  }
  initPlugins(function(fn) {
    // 执行传入的函数
    fn(E, $)
  })
  function initPlugins(_e) {
    // editor 绑定事件
    _e(function(E, $) {
      E.fn.init = function() {
        // 初始化 editor 默认配置
        this.initDefaultConfig()

        // 增加container
        this.addEditorContainer()

        // 增加编辑区域
        this.addTxt()

        // 增加menuContainer
        this.addMenuContainer()

        // 初始化菜单集合
        this.menus = {}

        // 初始化commandHooks
        this.commandHooks()
      }
    })
    // editor api
    _e(function(E, $) {
      // 预定义 ready 事件
      E.fn.ready = function(fn) {
        if (!this.readyFns) {
          this.readyFns = []
        }

        this.readyFns.push(fn)
      }

      // 处理ready事件
      E.fn.readyHeadler = function() {
        var fns = this.readyFns

        while (fns.length) {
          fns.shift().call(this)
        }
      }

      // 更新内容到 $valueContainer
      E.fn.updateValue = function() {
        var editor = this
        var $valueContainer = editor.$valueContainer
        var $txt = editor.txt.$txt

        if ($valueContainer === $txt) {
          // 传入生成编辑器的div，即是编辑区域
          return
        }

        var value = $txt.html()
        $valueContainer.val(value)
      }

      // 获取初始化的内容
      E.fn.getInitValue = function() {
        var editor = this
        var $valueContainer = editor.$valueContainer
        var currentValue = ''
        var nodeName = editor.valueNodeName
        if (nodeName === 'div') {
          currentValue = $valueContainer.html()
        } else if (nodeName === 'textarea') {
          currentValue = $valueContainer.val()
        }

        return currentValue
      }

      // 触发菜单updatestyle
      E.fn.updateMenuStyle = function() {
        var menus = this.menus

        $.each(menus, function(k, menu) {
          menu.updateSelected()
        })
      }

      // 除了传入的 menuIds，其他全部启用
      E.fn.enableMenusExcept = function(menuIds) {
        if (this._disabled) {
          // 编辑器处于禁用状态，则不执行改操作
          return
        }
        // menuIds参数：支持数组和字符串
        menuIds = menuIds || []
        if (typeof menuIds === 'string') {
          menuIds = [menuIds]
        }

        $.each(this.menus, function(k, menu) {
          if (menuIds.indexOf(k) >= 0) {
            return
          }
          menu.disabled(false)
        })
      }

      // 除了传入的 menuIds，其他全部禁用
      E.fn.disableMenusExcept = function(menuIds) {
        if (this._disabled) {
          // 编辑器处于禁用状态，则不执行改操作
          return
        }
        // menuIds参数：支持数组和字符串
        menuIds = menuIds || []
        if (typeof menuIds === 'string') {
          menuIds = [menuIds]
        }

        $.each(this.menus, function(k, menu) {
          if (menuIds.indexOf(k) >= 0) {
            return
          }
          menu.disabled(true)
        })
      }

      // 隐藏所有 dropPanel droplist modal
      E.fn.hideDropPanelAndModal = function() {
        var menus = this.menus

        $.each(menus, function(k, menu) {
          var m = menu.dropPanel || menu.dropList || menu.modal
          if (m && m.hide) {
            m.hide()
          }
        })
      }
    })
    // selection range API
    _e(function(E, $) {
      // 用到 w3c range 的函数，如果检测到浏览器不支持 w3c range，则赋值为空函数
      var ieRange = !E.w3cRange
      function emptyFn() {}

      // 设置或读取当前的range
      E.fn.currentRange = function(cr) {
        if (cr) {
          this._rangeData = cr
        } else {
          return this._rangeData
        }
      }

      // 将当前选区折叠
      E.fn.collapseRange = function(range, opt) {
        // opt 参数说明：'start'-折叠到开始; 'end'-折叠到结束
        opt = opt || 'end'
        opt = opt === 'start' ? true : false

        range = range || this.currentRange()

        if (range) {
          // 合并，保存
          range.collapse(opt)
          this.currentRange(range)
        }
      }

      // 获取选区的文字
      E.fn.getRangeText = ieRange
        ? emptyFn
        : function(range) {
            range = range || this.currentRange()
            if (!range) {
              return
            }
            return range.toString()
          }

      // 获取选区对应的DOM对象
      E.fn.getRangeElem = ieRange
        ? emptyFn
        : function(range) {
            range = range || this.currentRange()
            var dom = range.commonAncestorContainer

            if (dom.nodeType === 1) {
              return dom
            } else {
              return dom.parentNode
            }
          }

      // 选区内容是否为空？
      E.fn.isRangeEmpty = ieRange
        ? emptyFn
        : function(range) {
            range = range || this.currentRange()

            if (range && range.startContainer) {
              if (range.startContainer === range.endContainer) {
                if (range.startOffset === range.endOffset) {
                  return true
                }
              }
            }

            return false
          }

      // 保存选区数据
      E.fn.saveSelection = ieRange
        ? emptyFn
        : function(range) {
            var self = this,
              _parentElem,
              selection,
              txt = self.txt.$txt.get(0)

            if (range) {
              _parentElem = range.commonAncestorContainer
            } else {
              selection = document.getSelection()
              if (selection.getRangeAt && selection.rangeCount) {
                range = document.getSelection().getRangeAt(0)
                _parentElem = range.commonAncestorContainer
              }
            }
            // 确定父元素一定要包含在编辑器区域内
            if (
              _parentElem &&
              ($.contains(txt, _parentElem) || txt === _parentElem)
            ) {
              // 保存选择区域
              self.currentRange(range)
            }
          }

      // 恢复选中区域
      E.fn.restoreSelection = ieRange
        ? emptyFn
        : function(range) {
            var selection

            range = range || this.currentRange()

            if (!range) {
              return
            }

            // 使用 try catch 来防止 IE 某些情况报错
            try {
              selection = document.getSelection()
              selection.removeAllRanges()
              selection.addRange(range)
            } catch (ex) {
              E.error(
                '执行 editor.restoreSelection 时，IE可能会有异常，不影响使用'
              )
            }
          }

      // 根据elem恢复选区
      E.fn.restoreSelectionByElem = ieRange
        ? emptyFn
        : function(elem, opt) {
            // opt参数说明：'start'-折叠到开始，'end'-折叠到结束，'all'-全部选中
            if (!elem) {
              return
            }
            opt = opt || 'end' // 默认为折叠到结束

            // 根据elem获取选区
            this.setRangeByElem(elem)

            // 根据 opt 折叠选区
            if (opt === 'start') {
              this.collapseRange(this.currentRange(), 'start')
            }
            if (opt === 'end') {
              this.collapseRange(this.currentRange(), 'end')
            }

            // 恢复选区
            this.restoreSelection()
          }

      // 初始化选区
      E.fn.initSelection = ieRange
        ? emptyFn
        : function() {
            var editor = this
            if (editor.currentRange()) {
              //如果currentRange有值，则不用再初始化
              return
            }

            var range
            var $txt = editor.txt.$txt
            var $firstChild = $txt.children().first()

            if ($firstChild.length) {
              editor.restoreSelectionByElem($firstChild.get(0))
            }
          }

      // 根据元素创建选区
      E.fn.setRangeByElem = ieRange
        ? emptyFn
        : function(elem) {
            var editor = this
            var txtElem = editor.txt.$txt.get(0)
            if (!elem || !$.contains(txtElem, elem)) {
              return
            }

            // 找到elem的第一个 textNode 和 最后一个 textNode
            var firstTextNode = elem.firstChild
            while (firstTextNode) {
              if (firstTextNode.nodeType === 3) {
                break
              }
              // 继续向下
              firstTextNode = firstTextNode.firstChild
            }
            var lastTextNode = elem.lastChild
            while (lastTextNode) {
              if (lastTextNode.nodeType === 3) {
                break
              }
              // 继续向下
              lastTextNode = lastTextNode.lastChild
            }

            var range = document.createRange()
            if (firstTextNode && lastTextNode) {
              // 说明 elem 有内容，能取到子元素
              range.setStart(firstTextNode, 0)
              range.setEnd(lastTextNode, lastTextNode.textContent.length)
            } else {
              // 说明 elem 无内容
              range.setStart(elem, 0)
              range.setEnd(elem, 0)
            }

            // 保存选区
            editor.saveSelection(range)
          }
    })
    // selection range API - IE8及以下
    _e(function(E, $) {
      if (E.w3cRange) {
        // 说明支持 W3C 的range方法
        return
      }

      // -----------------IE8时，需要重写以下方法-------------------

      // 获取选区的文字
      E.fn.getRangeText = function(range) {
        range = range || this.currentRange()
        if (!range) {
          return
        }
        return range.text
      }

      // 获取选区对应的DOM对象
      E.fn.getRangeElem = function(range) {
        range = range || this.currentRange()
        if (!range) {
          return
        }
        var dom = range.parentElement()

        if (dom.nodeType === 1) {
          return dom
        } else {
          return dom.parentNode
        }
      }

      // 选区内容是否为空？
      E.fn.isRangeEmpty = function(range) {
        range = range || this.currentRange()

        if (range && range.text) {
          return false
        }

        return true
      }

      // 保存选区数据
      E.fn.saveSelection = function(range) {
        var self = this,
          _parentElem,
          selection,
          txt = self.txt.$txt.get(0)

        if (range) {
          _parentElem = range.parentElement()
        } else {
          range = document.selection.createRange()
          if (typeof range.parentElement === 'undefined') {
            //IE6、7中，insertImage后会执行此处
            //由于找不到range.parentElement，所以干脆将_parentElem赋值为null
            _parentElem = null
          } else {
            _parentElem = range.parentElement()
          }
        }

        // 确定父元素一定要包含在编辑器区域内
        if (
          _parentElem &&
          ($.contains(txt, _parentElem) || txt === _parentElem)
        ) {
          // 保存选择区域
          self.currentRange(range)
        }
      }

      // 恢复选中区域
      E.fn.restoreSelection = function(currentRange) {
        var editor = this,
          selection,
          range

        currentRange = currentRange || editor.currentRange()
        if (!currentRange) {
          return
        }

        range = document.selection.createRange()
        try {
          // 此处，plupload上传上传图片时，IE8-会报一个『参数无效』的错误
          range.setEndPoint('EndToEnd', currentRange)
        } catch (ex) {}

        if (currentRange.text.length === 0) {
          try {
            // IE8 插入表情会报错
            range.collapse(false)
          } catch (ex) {}
        } else {
          range.setEndPoint('StartToStart', currentRange)
        }
        range.select()
      }
    })
    // editor command hooks
    _e(function(E, $) {
      E.fn.commandHooks = function() {
        var editor = this
        var commandHooks = {}

        // insertHtml
        commandHooks.insertHtml = function(html) {
          var $elem = $(html)
          var rangeElem = editor.getRangeElem()
          var targetElem

          targetElem = editor.getLegalTags(rangeElem)
          if (!targetElem) {
            return
          }

          $(targetElem).after($elem)
        }

        // 保存到对象
        editor.commandHooks = commandHooks
      }
    })
    // editor command API
    _e(function(E, $) {
      // 基本命令
      E.fn.command = function(e, commandName, commandValue, callback) {
        var editor = this
        var hooks

        function commandFn() {
          if (!commandName) {
            return
          }
          if (editor.queryCommandSupported(commandName)) {
            // 默认命令
            document.execCommand(commandName, false, commandValue)
          } else {
            // hooks 命令
            hooks = editor.commandHooks
            if (commandName in hooks) {
              hooks[commandName](commandValue)
            }
          }
        }

        this.customCommand(e, commandFn, callback)
      }

      // 针对一个elem对象执行基础命令
      E.fn.commandForElem = function(
        elemOpt,
        e,
        commandName,
        commandValue,
        callback
      ) {
        // 取得查询elem的查询条件和验证函数
        var selector
        var check
        if (typeof elemOpt === 'string') {
          selector = elemOpt
        } else {
          selector = elemOpt.selector
          check = elemOpt.check
        }

        // 查询elem
        var rangeElem = this.getRangeElem()
        rangeElem = this.getSelfOrParentByName(rangeElem, selector, check)

        // 根据elem设置range
        if (rangeElem) {
          this.setRangeByElem(rangeElem)
        }

        // 然后执行基础命令
        this.command(e, commandName, commandValue, callback)
      }

      // 自定义命令
      E.fn.customCommand = function(e, commandFn, callback) {
        var editor = this
        var range = editor.currentRange()

        if (!range) {
          // 目前没有选区，则无法执行命令
          e && e.preventDefault()
          return
        }
        // 记录内容，以便撤销（执行命令之前就要记录）
        editor.undoRecord()

        // 恢复选区（有 range 参数）
        this.restoreSelection(range)

        // 执行命令事件
        commandFn.call(editor)

        // 保存选区（无参数，要从浏览器直接获取range信息）
        this.saveSelection()
        // 重新恢复选区（无参数，要取得刚刚从浏览器得到的range信息）
        this.restoreSelection()

        // 执行 callback
        if (callback && typeof callback === 'function') {
          callback.call(editor)
        }

        // 最后插入空行
        editor.txt.insertEmptyP()

        // 包裹暴露的img和text
        editor.txt.wrapImgAndText()

        // 更新内容
        editor.updateValue()

        // 更新菜单样式
        editor.updateMenuStyle()

        // 隐藏 dropPanel dropList modal  设置 200ms 间隔
        function hidePanelAndModal() {
          editor.hideDropPanelAndModal()
        }
        setTimeout(hidePanelAndModal, 200)

        if (e) {
          e.preventDefault()
        }
      }

      // 封装 document.queryCommandValue 函数
      // IE8 直接执行偶尔会报错，因此直接用 try catch 封装一下
      E.fn.queryCommandValue = function(commandName) {
        var result = ''
        try {
          result = document.queryCommandValue(commandName)
        } catch (ex) {}
        return result
      }

      // 封装 document.queryCommandState 函数
      // IE8 直接执行偶尔会报错，因此直接用 try catch 封装一下
      E.fn.queryCommandState = function(commandName) {
        var result = false
        try {
          result = document.queryCommandState(commandName)
        } catch (ex) {}
        return result
      }

      // 封装 document.queryCommandSupported 函数
      E.fn.queryCommandSupported = function(commandName) {
        var result = false
        try {
          result = document.queryCommandSupported(commandName)
        } catch (ex) {}
        return result
      }
    })
    // dom selector
    _e(function(E, $) {
      var matchesSelector

      // matchesSelector hook
      function _matchesSelectorForIE(selector) {
        var elem = this
        var $elems = $(selector)
        var result = false

        // 用jquery查找 selector 所有对象，如果其中有一个和传入 elem 相同，则证明 elem 符合 selector
        $elems.each(function() {
          if (this === elem) {
            result = true
            return false
          }
        })

        return result
      }

      // 从当前的elem，往上去查找合法标签 如 p head table blockquote ul ol 等
      E.fn.getLegalTags = function(elem) {
        var legalTags = this.config.legalTags
        if (!legalTags) {
          E.error('配置项中缺少 legalTags 的配置')
          return
        }
        return this.getSelfOrParentByName(elem, legalTags)
      }

      // 根据条件，查询自身或者父元素，符合即返回
      E.fn.getSelfOrParentByName = function(elem, selector, check) {
        if (!elem || !selector) {
          return
        }

        if (!matchesSelector) {
          // 定义 matchesSelector 函数
          matchesSelector =
            elem.webkitMatchesSelector ||
            elem.mozMatchesSelector ||
            elem.oMatchesSelector ||
            elem.matchesSelector
        }
        if (!matchesSelector) {
          // 如果浏览器本身不支持 matchesSelector 则使用自定义的hook
          matchesSelector = _matchesSelectorForIE
        }

        var txt = this.txt.$txt.get(0)

        while (elem && txt !== elem && $.contains(txt, elem)) {
          if (matchesSelector.call(elem, selector)) {
            // 符合 selector 查询条件

            if (!check) {
              // 没有 check 验证函数，直接返回即可
              return elem
            }

            if (check(elem)) {
              // 如果有 check 验证函数，还需 check 函数的确认
              return elem
            }
          }

          // 如果上一步没经过验证，则将跳转到父元素
          elem = elem.parentNode
        }

        return
      }
    })
    // undo redo
    _e(function(E, $) {
      var length = 20 // 缓存的最大长度
      function _getRedoList(editor) {
        if (editor._redoList == null) {
          editor._redoList = []
        }
        return editor._redoList
      }
      function _getUndoList(editor) {
        if (editor._undoList == null) {
          editor._undoList = []
        }
        return editor._undoList
      }

      // 数据处理
      function _handle(editor, data, type) {
        // var range = data.range;
        // var range2 = range.cloneRange && range.cloneRange();
        var val = data.val
        var html = editor.txt.$txt.html()

        if (val == null) {
          return
        }

        if (val === html) {
          if (type === 'redo') {
            editor.redo()
            return
          } else if (type === 'undo') {
            editor.undo()
            return
          } else {
            return
          }
        }

        // 保存数据
        editor.txt.$txt.html(val)
        // 更新数据到textarea（有必要的话）
        editor.updateValue()

        // onchange 事件
        if (editor.onchange && typeof editor.onchange === 'function') {
          editor.onchange.call(editor)
        }

        // ?????
        // 注释：$txt 被重新赋值之后，range会被重置，cloneRange() 也不好使
        // // 重置选区
        // if (range2) {
        //     editor.restoreSelection(range2);
        // }
      }

      // 记录
      E.fn.undoRecord = function() {
        var editor = this
        var $txt = editor.txt.$txt
        var val = $txt.html()
        var undoList = _getUndoList(editor)
        var redoList = _getRedoList(editor)
        var currentVal = undoList.length ? undoList[0] : ''

        if (val === currentVal.val) {
          return
        }

        // 清空 redolist
        if (redoList.length) {
          redoList = []
        }

        // 添加数据到 undoList
        undoList.unshift({
          range: editor.currentRange(), // 将当前的range也记录下
          val: val
        })

        // 限制 undoList 长度
        if (undoList.length > length) {
          undoList.pop()
        }
      }

      // undo 操作
      E.fn.undo = function() {
        var editor = this
        var undoList = _getUndoList(editor)
        var redoList = _getRedoList(editor)

        if (!undoList.length) {
          return
        }

        // 取出 undolist 第一个值，加入 redolist
        var data = undoList.shift()
        redoList.unshift(data)

        // 并修改编辑器的内容
        _handle(this, data, 'undo')
      }

      // redo 操作
      E.fn.redo = function() {
        var editor = this
        var undoList = _getUndoList(editor)
        var redoList = _getRedoList(editor)
        if (!redoList.length) {
          return
        }

        // 取出 redolist 第一个值，加入 undolist
        var data = redoList.shift()
        undoList.unshift(data)

        // 并修改编辑器的内容
        _handle(this, data, 'redo')
      }
    })
    // 暴露给用户的 API
    _e(function(E, $) {
      // 创建编辑器
      E.fn.create = function() {
        var editor = this
        // 检查 E.$body 是否有值
        // 如果在 body 之前引用了 js 文件，body 尚未加载，可能没有值
        if (!E.$body || E.$body.length === 0) {
          E.$body = $('body')
          E.$document = $(document)
          E.$window = $(window)
        }

        // 执行 addMenus 之前：
        // 1. 允许用户修改 editor.UI 自定义配置UI
        // 2. 允许用户通过修改 editor.menus 来自定义配置菜单
        // 因此要在 create 时执行，而不是 init
        editor.addMenus()

        // 渲染
        editor.renderMenus()
        editor.renderMenuContainer()
        editor.renderTxt()
        editor.renderEditorContainer()

        // 绑定事件
        editor.eventMenus()
        editor.eventMenuContainer()
        editor.eventTxt()

        // 处理ready事件
        editor.readyHeadler()

        // 初始化选区
        editor.initSelection()

        // $txt 快捷方式
        editor.$txt = editor.txt.$txt

        // 执行用户自定义事件，通过 E.ready() 添加
        var _plugins = E._plugins
        if (_plugins && _plugins.length) {
          $.each(_plugins, function(k, val) {
            val.call(editor)
          })
        }
      }

      // 禁用编辑器
      E.fn.disable = function() {
        this.txt.$txt.removeAttr('contenteditable')
        this.disableMenusExcept()

        // 先禁用，再记录状态
        this._disabled = true
      }
      // 启用编辑器
      E.fn.enable = function() {
        // 先解除状态记录，再启用
        this._disabled = false
        this.txt.$txt.attr('contenteditable', 'true')
        this.enableMenusExcept()
      }

      // 销毁编辑器
      E.fn.destroy = function() {
        var self = this
        var $valueContainer = self.$valueContainer
        var $editorContainer = self.$editorContainer
        var valueNodeName = self.valueNodeName

        if (valueNodeName === 'div') {
          // div 生成的编辑器
          $valueContainer.removeAttr('contenteditable')
          $editorContainer.after($valueContainer)
          $editorContainer.hide()
        } else {
          // textarea 生成的编辑器
          $valueContainer.show()
          $editorContainer.hide()
        }
      }

      // 撤销 销毁编辑器
      E.fn.undestroy = function() {
        var self = this
        var $valueContainer = self.$valueContainer
        var $editorContainer = self.$editorContainer
        var $menuContainer = self.menuContainer.$menuContainer
        var valueNodeName = self.valueNodeName

        if (valueNodeName === 'div') {
          // div 生成的编辑器
          $valueContainer.attr('contenteditable', 'true')
          $menuContainer.after($valueContainer)
          $editorContainer.show()
        } else {
          // textarea 生成的编辑器
          $valueContainer.hide()
          $editorContainer.show()
        }
      }

      // 清空内容的快捷方式
      E.fn.clear = function() {
        var editor = this
        var $txt = editor.txt.$txt
        $txt.html('<p><br></p>')
        editor.restoreSelectionByElem($txt.find('p').get(0))
      }
    })
    // menuContainer 构造函数
    _e(function(E, $) {
      // 定义构造函数
      var MenuContainer = function(editor) {
        this.editor = editor
        this.init()
      }

      MenuContainer.fn = MenuContainer.prototype

      // 暴露给 E 即 window.wangEditor
      E.MenuContainer = MenuContainer
    })
    // MenuContainer.fn bind fn
    _e(function(E, $) {
      var MenuContainer = E.MenuContainer

      // 初始化
      MenuContainer.fn.init = function() {
        var self = this
        var $menuContainer = $(
          '<div class="wangEditor-menu-container clearfix"></div>'
        )

        self.$menuContainer = $menuContainer

        // change shadow
        self.changeShadow()
      }

      // 编辑区域滚动时，增加shadow
      MenuContainer.fn.changeShadow = function() {
        var $menuContainer = this.$menuContainer
        var editor = this.editor
        var $txt = editor.txt.$txt

        $txt.on('scroll', function() {
          if ($txt.scrollTop() > 10) {
            $menuContainer.addClass('wangEditor-menu-shadow')
          } else {
            $menuContainer.removeClass('wangEditor-menu-shadow')
          }
        })
      }
    })
    // MenuContainer.fn API
    _e(function(E, $) {
      var MenuContainer = E.MenuContainer

      MenuContainer.fn.render = function() {
        var $menuContainer = this.$menuContainer
        var $editorContainer = this.editor.$editorContainer

        $editorContainer.append($menuContainer)
      }

      // 获取菜单栏的高度
      MenuContainer.fn.height = function() {
        var $menuContainer = this.$menuContainer
        return $menuContainer.height()
      }

      // 添加菜单
      MenuContainer.fn.appendMenu = function(groupIdx, menu) {
        // 判断是否需要新增一个菜单组
        this._addGroup(groupIdx)
        // 增加菜单（返回 $menuItem）
        return this._addOneMenu(menu)
      }
      MenuContainer.fn._addGroup = function(groupIdx) {
        var $menuContainer = this.$menuContainer
        var $menuGroup
        if (!this.$currentGroup || this.currentGroupIdx !== groupIdx) {
          $menuGroup = $('<div class="menu-group clearfix"></div>')
          $menuContainer.append($menuGroup)

          this.$currentGroup = $menuGroup
          this.currentGroupIdx = groupIdx
        }
      }
      MenuContainer.fn._addOneMenu = function(menu) {
        var $menuNormal = menu.$domNormal
        var $menuSelected = menu.$domSelected

        var $menuGroup = this.$currentGroup
        var $item = $('<div class="menu-item clearfix"></div>')
        $menuSelected.hide()
        $item.append($menuNormal).append($menuSelected)
        $menuGroup.append($item)

        return $item
      }
    })
    // menu 构造函数
    _e(function(E, $) {
      // 定义构造函数
      var Menu = function(opt) {
        this.editor = opt.editor
        this.id = opt.id
        this.title = opt.title
        this.$domNormal = opt.$domNormal
        this.$domSelected = opt.$domSelected || opt.$domNormal

        // document.execCommand 的参数
        this.commandName = opt.commandName
        this.commandValue = opt.commandValue
        this.commandNameSelected = opt.commandNameSelected || opt.commandName
        this.commandValueSelected = opt.commandValueSelected || opt.commandValue
      }

      Menu.fn = Menu.prototype

      // 暴露给 E 即 window.wangEditor
      E.Menu = Menu
    })
    // Menu.fn 初始化绑定的事件
    _e(function(E, $) {
      var Menu = E.Menu

      // 初始化UI
      Menu.fn.initUI = function() {
        var editor = this.editor
        var uiConfig = editor.UI.menus
        var menuId = this.id
        var menuUI = uiConfig[menuId]

        if (this.$domNormal && this.$domSelected) {
          // 自定义的菜单中，已经传入了 $dom 无需从配置文件中查找生成
          return
        }

        if (menuUI == null) {
          E.warn(
            'editor.UI配置中，没有菜单 "' + menuId + '" 的UI配置，只能取默认值'
          )

          // 必须写成 uiConfig['default'];
          // 写成 uiConfig.default IE8会报错
          menuUI = uiConfig['default']
        }

        // 正常状态
        this.$domNormal = $(menuUI.normal)

        // 选中状态
        if (/^\./.test(menuUI.selected)) {
          // 增加一个样式
          this.$domSelected = this.$domNormal
            .clone()
            .addClass(menuUI.selected.slice(1))
        } else {
          // 一个新的dom对象
          this.$domSelected = $(menuUI.selected)
        }
      }
    })
    // Menu.fn API
    _e(function(E, $) {
      var Menu = E.Menu

      // 渲染菜单
      Menu.fn.render = function(groupIdx) {
        // 渲染UI
        this.initUI()

        var editor = this.editor
        var menuContainer = editor.menuContainer
        var $menuItem = menuContainer.appendMenu(groupIdx, this)
        var onRender = this.onRender

        // 渲染tip
        this._renderTip($menuItem)

        // 执行 onRender 函数
        if (onRender && typeof onRender === 'function') {
          onRender.call(this)
        }
      }
      Menu.fn._renderTip = function($menuItem) {
        var self = this
        var editor = self.editor
        var title = self.title
        var $tip = $('<div class="menu-tip"></div>')
        // var $triangle = $('<i class="tip-triangle"></i>'); // 小三角

        // 计算 tip 宽度
        var $tempDiv
        if (!self.tipWidth) {
          // 设置一个纯透明的 p（absolute;top:-10000px;不会显示在内容区域）
          // 内容赋值为 title ，为了计算tip宽度
          $tempDiv = $(
            '<p style="opacity:0; filter:Alpha(opacity=0); position:absolute;top:-10000px;">' +
              title +
              '</p>'
          )
          // 先添加到body，计算完再 remove
          E.$body.append($tempDiv)
          editor.ready(function() {
            var editor = this
            var titleWidth = $tempDiv.outerWidth() + 5 // 多出 5px 的冗余
            var currentWidth = $tip.outerWidth()
            var currentMarginLeft = parseFloat($tip.css('margin-left'), 10)
            // 计算完，拿到数据，则弃用
            $tempDiv.remove()
            $tempDiv = null

            // 重新设置样式
            $tip.css({
              width: titleWidth,
              'margin-left': currentMarginLeft + (currentWidth - titleWidth) / 2
            })

            // 存储
            self.tipWidth = titleWidth
          })
        }

        // $tip.append($triangle);
        $tip.append(title)
        $menuItem.append($tip)

        function show() {
          $tip.show()
        }
        function hide() {
          $tip.hide()
        }

        var timeoutId
        $menuItem
          .find('a')
          .on('mouseenter', function(e) {
            if (!self.active() && !self.disabled()) {
              timeoutId = setTimeout(show, 200)
            }
          })
          .on('mouseleave', function(e) {
            timeoutId && clearTimeout(timeoutId)
            hide()
          })
          .on('click', hide)
      }

      // 绑定事件
      Menu.fn.bindEvent = function() {
        var self = this

        var $domNormal = self.$domNormal
        var $domSelected = self.$domSelected

        // 试图获取该菜单定义的事件（未selected），没有则自己定义
        var clickEvent = self.clickEvent
        if (!clickEvent) {
          clickEvent = function(e) {
            // -----------dropPanel dropList modal-----------
            var dropObj = self.dropPanel || self.dropList || self.modal
            if (dropObj && dropObj.show) {
              if (dropObj.isShowing) {
                dropObj.hide()
              } else {
                dropObj.show()
              }
              return
            }

            // -----------command-----------
            var editor = self.editor
            var commandName
            var commandValue

            var selected = self.selected
            if (selected) {
              commandName = self.commandNameSelected
              commandValue = self.commandValueSelected
            } else {
              commandName = self.commandName
              commandValue = self.commandValue
            }

            if (commandName) {
              // 执行命令
              editor.command(e, commandName, commandValue)
            } else {
              // 提示
              E.warn('菜单 "' + self.id + '" 未定义click事件')
              e.preventDefault()
            }
          }
        }
        // 获取菜单定义的selected情况下的点击事件
        var clickEventSelected = self.clickEventSelected || clickEvent

        // 将事件绑定到菜单dom上
        $domNormal.click(function(e) {
          if (!self.disabled()) {
            clickEvent.call(self, e)
            self.updateSelected()
          }
          e.preventDefault()
        })
        $domSelected.click(function(e) {
          if (!self.disabled()) {
            clickEventSelected.call(self, e)
            self.updateSelected()
          }
          e.preventDefault()
        })
      }

      // 更新选中状态
      Menu.fn.updateSelected = function() {
        var self = this
        var editor = self.editor

        // 试图获取用户自定义的判断事件
        var updateSelectedEvent = self.updateSelectedEvent
        if (!updateSelectedEvent) {
          // 用户未自定义，则设置默认值
          updateSelectedEvent = function() {
            var self = this
            var editor = self.editor
            var commandName = self.commandName
            var commandValue = self.commandValue

            if (commandValue) {
              if (
                editor.queryCommandValue(commandName).toLowerCase() ===
                commandValue.toLowerCase()
              ) {
                return true
              }
            } else if (editor.queryCommandState(commandName)) {
              return true
            }

            return false
          }
        }

        // 获取结果
        var result = updateSelectedEvent.call(self)
        result = !!result

        // 存储结果、显示效果
        self.changeSelectedState(result)
      }

      // 切换选中状态、显示效果
      Menu.fn.changeSelectedState = function(state) {
        var self = this
        var selected = self.selected

        if (state != null && typeof state === 'boolean') {
          if (selected === state) {
            // 计算结果和当前的状态一样
            return
          }
          // 存储结果
          self.selected = state

          // 切换菜单的显示
          if (state) {
            // 选中
            self.$domNormal.hide()
            self.$domSelected.show()
          } else {
            // 未选中
            self.$domNormal.show()
            self.$domSelected.hide()
          }
        } // if
      }

      // 点击菜单，显示了 dropPanel modal 时，菜单的状态
      Menu.fn.active = function(active) {
        if (active == null) {
          return this._activeState
        }
        this._activeState = active
      }
      Menu.fn.activeStyle = function(active) {
        var selected = this.selected
        var $dom = this.$domNormal
        var $domSelected = this.$domSelected

        if (active) {
          $dom.addClass('active')
          $domSelected.addClass('active')
        } else {
          $dom.removeClass('active')
          $domSelected.removeClass('active')
        }

        // 记录状态 （ menu hover 时会取状态用 ）
        this.active(active)
      }

      // 菜单的启用和禁用
      Menu.fn.disabled = function(opt) {
        // 参数为空，取值
        if (opt == null) {
          return !!this._disabled
        }

        if (this._disabled === opt) {
          // 要设置的参数值和当前参数只一样，无需再次设置
          return
        }

        var $dom = this.$domNormal
        var $domSelected = this.$domSelected

        // 设置样式
        if (opt) {
          $dom.addClass('disable')
          $domSelected.addClass('disable')
        } else {
          $dom.removeClass('disable')
          $domSelected.removeClass('disable')
        }

        // 存储
        this._disabled = opt
      }
    })
    // dropList 构造函数
    _e(function(E, $) {
      // 定义构造函数
      var DropList = function(editor, menu, opt) {
        this.editor = editor
        this.menu = menu

        // list 的数据源，格式 {'commandValue': 'title', ...}
        this.data = opt.data
        // 要为每个item自定义的模板
        this.tpl = opt.tpl
        // 为了执行 editor.commandForElem 而传入的elem查询方式
        this.selectorForELemCommand = opt.selectorForELemCommand

        // 执行事件前后的钩子
        this.beforeEvent = opt.beforeEvent
        this.afterEvent = opt.afterEvent

        // 初始化
        this.init()
      }

      DropList.fn = DropList.prototype

      // 暴露给 E 即 window.wangEditor
      E.DropList = DropList
    })
    // dropList fn bind
    _e(function(E, $) {
      var DropList = E.DropList

      // init
      DropList.fn.init = function() {
        var self = this

        // 生成dom对象
        self.initDOM()

        // 绑定command事件
        self.bindEvent()

        // 声明隐藏的事件
        self.initHideEvent()
      }

      // 初始化dom结构
      DropList.fn.initDOM = function() {
        var self = this
        var data = self.data
        var tpl = self.tpl || '<span>{#title}</span>'
        var $list = $('<div class="wangEditor-drop-list clearfix"></div>')

        var itemContent
        var $item
        $.each(data, function(commandValue, title) {
          itemContent = tpl
            .replace(/{#commandValue}/gi, commandValue)
            .replace(/{#title}/gi, title)
          $item = $('<a href="#" commandValue="' + commandValue + '"></a>')
          $item.append(itemContent)
          $list.append($item)
        })

        self.$list = $list
      }

      // 绑定事件
      DropList.fn.bindEvent = function() {
        var self = this
        var editor = self.editor
        var menu = self.menu
        var commandName = menu.commandName
        var selectorForELemCommand = self.selectorForELemCommand
        var $list = self.$list

        // 执行事件前后的钩子函数
        var beforeEvent = self.beforeEvent
        var afterEvent = self.afterEvent

        $list.on('click', 'a[commandValue]', function(e) {
          // 正式命令执行之前
          if (beforeEvent && typeof beforeEvent === 'function') {
            beforeEvent.call(e)
          }

          // 执行命令
          var commandValue = $(e.currentTarget).attr('commandValue')
          if (
            menu.selected &&
            editor.isRangeEmpty() &&
            selectorForELemCommand
          ) {
            // 当前处于选中状态，并且选中内容为空
            editor.commandForElem(
              selectorForELemCommand,
              e,
              commandName,
              commandValue
            )
          } else {
            // 当前未处于选中状态，或者有选中内容。则执行默认命令
            editor.command(e, commandName, commandValue)
          }

          // 正式命令之后的钩子
          if (afterEvent && typeof afterEvent === 'function') {
            afterEvent.call(e)
          }
        })
      }

      // 点击其他地方，立即隐藏 droplist
      DropList.fn.initHideEvent = function() {
        var self = this

        // 获取 list elem
        var thisList = self.$list.get(0)

        E.$body.on('click', function(e) {
          if (!self.isShowing) {
            return
          }
          var trigger = e.target

          // 获取菜单elem
          var menu = self.menu
          var menuDom
          if (menu.selected) {
            menuDom = menu.$domSelected.get(0)
          } else {
            menuDom = menu.$domNormal.get(0)
          }

          if (menuDom === trigger || $.contains(menuDom, trigger)) {
            // 说明由本菜单点击触发的
            return
          }

          if (thisList === trigger || $.contains(thisList, trigger)) {
            // 说明由本list点击触发的
            return
          }

          // 其他情况，隐藏 list
          self.hide()
        })

        E.$window.scroll(function() {
          self.hide()
        })

        E.$window.on('resize', function() {
          self.hide()
        })
      }
    })
    // dropListfn api
    _e(function(E, $) {
      var DropList = E.DropList

      // 渲染
      DropList.fn._render = function() {
        var self = this
        var editor = self.editor
        var $list = self.$list

        // 渲染到页面
        editor.$editorContainer.append($list)

        // 记录状态
        self.rendered = true
      }

      // 定位
      DropList.fn._position = function() {
        var self = this
        var $list = self.$list
        var editor = self.editor
        var menu = self.menu
        var $menuContainer = editor.menuContainer.$menuContainer
        var $menuDom = menu.selected ? menu.$domSelected : menu.$domNormal
        // 注意这里的 offsetParent() 要返回 .menu-item 的 position
        // 因为 .menu-item 是 position:relative
        var menuPosition = $menuDom.offsetParent().position()

        // 取得 menu 的位置、尺寸属性
        var menuTop = menuPosition.top
        var menuLeft = menuPosition.left
        var menuHeight = $menuDom.offsetParent().height()
        var menuWidth = $menuDom.offsetParent().width()

        // 取得 list 的尺寸属性
        var listWidth = $list.outerWidth()
        // var listHeight = $list.outerHeight();

        // 取得 $txt 的尺寸
        var txtWidth = editor.txt.$txt.outerWidth()

        // ------------开始计算-------------

        // 初步计算 list 位置属性
        var top = menuTop + menuHeight
        var left = menuLeft + menuWidth / 2
        var marginLeft = 0 - menuWidth / 2

        // 如果超出了有边界，则要左移（且和右侧有间隙）
        var valWithTxt = left + listWidth - txtWidth
        if (valWithTxt > -10) {
          marginLeft = marginLeft - valWithTxt - 10
        }
        // 设置样式
        $list.css({
          top: top,
          left: left,
          'margin-left': marginLeft
        })

        // 如果因为向下滚动而导致菜单fixed，则再加一步处理
        if (editor._isMenufixed) {
          top =
            top +
            ($menuContainer.offset().top +
              $menuContainer.outerHeight() -
              $list.offset().top)

          // 重新设置top
          $list.css({
            top: top
          })
        }
      }

      // 显示
      DropList.fn.show = function() {
        var self = this
        var menu = self.menu
        if (!self.rendered) {
          // 第一次show之前，先渲染
          self._render()
        }

        if (self.isShowing) {
          return
        }

        var $list = self.$list
        $list.show()

        // 定位
        self._position()

        // 记录状态
        self.isShowing = true

        // 菜单状态
        menu.activeStyle(true)
      }

      // 隐藏
      DropList.fn.hide = function() {
        var self = this
        var menu = self.menu
        if (!self.isShowing) {
          return
        }

        var $list = self.$list
        $list.hide()

        // 记录状态
        self.isShowing = false

        // 菜单状态
        menu.activeStyle(false)
      }
    })
    // dropPanel 构造函数
    _e(function(E, $) {
      // 定义构造函数
      var DropPanel = function(editor, menu, opt) {
        this.editor = editor
        this.menu = menu
        this.$content = opt.$content
        this.width = opt.width || 200
        this.height = opt.height
        this.onRender = opt.onRender

        // init
        this.init()
      }

      DropPanel.fn = DropPanel.prototype

      // 暴露给 E 即 window.wangEditor
      E.DropPanel = DropPanel
    })
    // dropPanel fn bind
    _e(function(E, $) {
      var DropPanel = E.DropPanel

      // init
      DropPanel.fn.init = function() {
        var self = this

        // 生成dom对象
        self.initDOM()

        // 声明隐藏的事件
        self.initHideEvent()
      }

      // init DOM
      DropPanel.fn.initDOM = function() {
        var self = this
        var $content = self.$content
        var width = self.width
        var height = self.height
        var $panel = $('<div class="wangEditor-drop-panel clearfix"></div>')
        var $triangle = $('<div class="tip-triangle"></div>')

        $panel.css({
          width: width,
          height: height ? height : 'auto'
        })
        $panel.append($triangle)
        $panel.append($content)

        // 添加对象数据
        self.$panel = $panel
        self.$triangle = $triangle
      }

      // 点击其他地方，立即隐藏 dropPanel
      DropPanel.fn.initHideEvent = function() {
        var self = this

        // 获取 panel elem
        var thisPanle = self.$panel.get(0)

        E.$body.on('click', function(e) {
          if (!self.isShowing) {
            return
          }
          var trigger = e.target

          // 获取菜单elem
          var menu = self.menu
          var menuDom
          if (menu.selected) {
            menuDom = menu.$domSelected.get(0)
          } else {
            menuDom = menu.$domNormal.get(0)
          }

          if (menuDom === trigger || $.contains(menuDom, trigger)) {
            // 说明由本菜单点击触发的
            return
          }

          if (thisPanle === trigger || $.contains(thisPanle, trigger)) {
            // 说明由本panel点击触发的
            return
          }

          // 其他情况，隐藏 panel
          self.hide()
        })

        E.$window.scroll(function(e) {
          self.hide()
        })

        E.$window.on('resize', function() {
          self.hide()
        })
      }
    })
    // dropPanel fn api
    _e(function(E, $) {
      var DropPanel = E.DropPanel

      // 渲染
      DropPanel.fn._render = function() {
        var self = this
        var onRender = self.onRender
        var editor = self.editor
        var $panel = self.$panel

        // 渲染到页面
        editor.$editorContainer.append($panel)

        // 渲染后的回调事件
        onRender && onRender.call(self)

        // 记录状态
        self.rendered = true
      }

      // 定位
      DropPanel.fn._position = function() {
        var self = this
        var $panel = self.$panel
        var $triangle = self.$triangle
        var editor = self.editor
        var $menuContainer = editor.menuContainer.$menuContainer
        var menu = self.menu
        var $menuDom = menu.selected ? menu.$domSelected : menu.$domNormal
        // 注意这里的 offsetParent() 要返回 .menu-item 的 position
        // 因为 .menu-item 是 position:relative
        var menuPosition = $menuDom.offsetParent().position()

        // 取得 menu 的位置、尺寸属性
        var menuTop = menuPosition.top
        var menuLeft = menuPosition.left
        var menuHeight = $menuDom.offsetParent().height()
        var menuWidth = $menuDom.offsetParent().width()

        // 取得 panel 的尺寸属性
        var panelWidth = $panel.outerWidth()
        // var panelHeight = $panel.outerHeight();

        // 取得 $txt 的尺寸
        var txtWidth = editor.txt.$txt.outerWidth()

        // ------------开始计算-------------

        // 初步计算 panel 位置属性
        var top = menuTop + menuHeight
        var left = menuLeft + menuWidth / 2
        var marginLeft = 0 - panelWidth / 2
        var marginLeft2 = marginLeft // 下文用于和 marginLeft 比较，来设置三角形tip的位置

        // 如果超出了左边界，则移动回来（要和左侧有10px间隙）
        if (0 - marginLeft > left - 10) {
          marginLeft = 0 - (left - 10)
        }

        // 如果超出了有边界，则要左移（且和右侧有10px间隙）
        var valWithTxt = left + panelWidth + marginLeft - txtWidth
        if (valWithTxt > -10) {
          marginLeft = marginLeft - valWithTxt - 10
        }

        // 设置样式
        $panel.css({
          top: top,
          left: left,
          'margin-left': marginLeft
        })

        // 如果因为向下滚动而导致菜单fixed，则再加一步处理
        if (editor._isMenufixed) {
          top =
            top +
            ($menuContainer.offset().top +
              $menuContainer.outerHeight() -
              $panel.offset().top)

          // 重新设置top
          $panel.css({
            top: top
          })
        }

        // 设置三角形 tip 的位置
        $triangle.css({
          'margin-left': marginLeft2 - marginLeft - 5
        })
      }

      // focus 第一个 input
      DropPanel.fn.focusFirstInput = function() {
        var self = this
        var $panel = self.$panel
        $panel.find('input[type=text],textarea').each(function() {
          var $input = $(this)
          if ($input.attr('disabled') == null) {
            $input.focus()
            return false
          }
        })
      }

      // 显示
      DropPanel.fn.show = function() {
        var self = this
        var menu = self.menu
        if (!self.rendered) {
          // 第一次show之前，先渲染
          self._render()
        }

        if (self.isShowing) {
          return
        }

        var $panel = self.$panel
        $panel.show()

        // 定位
        self._position()

        // 记录状态
        self.isShowing = true

        // 菜单状态
        menu.activeStyle(true)

        if (E.w3cRange) {
          // 高级浏览器
          self.focusFirstInput()
        } else {
          // 兼容 IE8 input placeholder
          E.placeholderForIE8($panel)
        }
      }

      // 隐藏
      DropPanel.fn.hide = function() {
        var self = this
        var menu = self.menu
        if (!self.isShowing) {
          return
        }

        var $panel = self.$panel
        $panel.hide()

        // 记录状态
        self.isShowing = false

        // 菜单状态
        menu.activeStyle(false)
      }
    })
    // modal 构造函数
    _e(function(E, $) {
      // 定义构造函数
      var Modal = function(editor, menu, opt) {
        this.editor = editor
        this.menu = menu
        this.$content = opt.$content

        this.init()
      }

      Modal.fn = Modal.prototype

      // 暴露给 E 即 window.wangEditor
      E.Modal = Modal
    })
    // modal fn bind
    _e(function(E, $) {
      var Modal = E.Modal

      Modal.fn.init = function() {
        var self = this

        // 初始化dom
        self.initDom()

        // 初始化隐藏事件
        self.initHideEvent()
      }

      // 初始化dom
      Modal.fn.initDom = function() {
        var self = this
        var $content = self.$content
        var $modal = $('<div class="wangEditor-modal"></div>')
        var $close = $(
          '<div class="wangEditor-modal-close"><i class="wangeditor-menu-img-cancel-circle"></i></div>'
        )

        $modal.append($close)
        $modal.append($content)

        // 记录数据
        self.$modal = $modal
        self.$close = $close
      }

      // 初始化隐藏事件
      Modal.fn.initHideEvent = function() {
        var self = this
        var $close = self.$close
        var modal = self.$modal.get(0)

        // 点击 $close 按钮，隐藏
        $close.click(function() {
          self.hide()
        })

        // 点击其他部分，隐藏
        E.$body.on('click', function(e) {
          if (!self.isShowing) {
            return
          }
          var trigger = e.target

          // 获取菜单elem
          var menu = self.menu
          var menuDom
          if (menu) {
            if (menu.selected) {
              menuDom = menu.$domSelected.get(0)
            } else {
              menuDom = menu.$domNormal.get(0)
            }

            if (menuDom === trigger || $.contains(menuDom, trigger)) {
              // 说明由本菜单点击触发的
              return
            }
          }

          if (modal === trigger || $.contains(modal, trigger)) {
            // 说明由本panel点击触发的
            return
          }

          // 其他情况，隐藏 panel
          self.hide()
        })
      }
    })
    // modal fn api
    _e(function(E, $) {
      var Modal = E.Modal

      // 渲染
      Modal.fn._render = function() {
        var self = this
        var editor = self.editor
        var $modal = self.$modal

        // $modal的z-index，在配置的z-index基础上再 +10
        $modal.css('z-index', editor.config.zindex + 10 + '')

        // 渲染到body最后面
        E.$body.append($modal)

        // 记录状态
        self.rendered = true
      }

      // 定位
      Modal.fn._position = function() {
        var self = this
        var $modal = self.$modal
        var top = $modal.offset().top
        var width = $modal.outerWidth()
        var height = $modal.outerHeight()
        var marginLeft = 0 - width / 2
        var marginTop = 0 - height / 2
        var sTop = E.$window.scrollTop()

        // 保证modal最顶部，不超过浏览器上边框
        if (height / 2 > top) {
          marginTop = 0 - top
        }

        $modal.css({
          'margin-left': marginLeft + 'px',
          'margin-top': marginTop + sTop + 'px'
        })
      }

      // 显示
      Modal.fn.show = function() {
        var self = this
        var menu = self.menu
        if (!self.rendered) {
          // 第一次show之前，先渲染
          self._render()
        }

        if (self.isShowing) {
          return
        }
        // 记录状态
        self.isShowing = true

        var $modal = self.$modal
        $modal.show()

        // 定位
        self._position()

        // 激活菜单状态
        menu && menu.activeStyle(true)
      }

      // 隐藏
      Modal.fn.hide = function() {
        var self = this
        var menu = self.menu
        if (!self.isShowing) {
          return
        }
        // 记录状态
        self.isShowing = false

        // 隐藏
        var $modal = self.$modal
        $modal.hide()

        // 菜单状态
        menu && menu.activeStyle(false)
      }
    })
    // txt 构造函数
    _e(function(E, $) {
      // 定义构造函数
      var Txt = function(editor) {
        this.editor = editor

        // 初始化
        this.init()
      }

      Txt.fn = Txt.prototype

      // 暴露给 E 即 window.wangEditor
      E.Txt = Txt
    })
    // Txt.fn bind fn
    _e(function(E, $) {
      var Txt = E.Txt

      // 初始化
      Txt.fn.init = function() {
        var self = this
        var editor = self.editor
        var $valueContainer = editor.$valueContainer
        var currentValue = editor.getInitValue()
        var $txt

        if ($valueContainer.get(0).nodeName === 'DIV') {
          // 如果传入生成编辑器的元素就是div，则直接使用
          $txt = $valueContainer
          $txt.addClass('wangEditor-txt')
          $txt.attr('contentEditable', 'true')
        } else {
          // 如果不是div（是textarea），则创建一个div
          $txt = $(
            '<div class="wangEditor-txt" contentEditable="true">' +
              currentValue +
              '</div>'
          )
        }

        // 试图最后插入一个空行，ready之后才行
        editor.ready(function() {
          self.insertEmptyP()
        })

        self.$txt = $txt

        // 删除时，如果没有内容了，就添加一个 <p><br></p>
        self.contentEmptyHandle()

        // enter时，不能使用 div 换行
        self.bindEnterForDiv()

        // enter时，用 p 包裹 text
        self.bindEnterForText()

        // tab 插入4个空格
        self.bindTabEvent()

        // 处理粘贴内容
        self.bindPasteFilter()

        // $txt.formatText() 方法
        self.bindFormatText()

        // 定义 $txt.html() 方法
        self.bindHtml()
      }

      // 删除时，如果没有内容了，就添加一个 <p><br></p>
      Txt.fn.contentEmptyHandle = function() {
        var self = this
        var editor = self.editor
        var $txt = self.$txt
        var $p

        $txt.on('keydown', function(e) {
          if (e.keyCode !== 8) {
            return
          }
          var txtHtml = $.trim($txt.html().toLowerCase())
          if (txtHtml === '<p><br></p>') {
            // 如果最后还剩余一个空行，就不再继续删除了
            e.preventDefault()
            return
          }
        })

        $txt.on('keyup', function(e) {
          if (e.keyCode !== 8) {
            return
          }
          var txtHtml = $.trim($txt.html().toLowerCase())
          // ff时用 txtHtml === '<br>' 判断，其他用 !txtHtml 判断
          if (!txtHtml || txtHtml === '<br>') {
            // 内容空了
            $p = $('<p><br/></p>')
            $txt.html('') // 一定要先清空，否则在 ff 下有问题
            $txt.append($p)
            editor.restoreSelectionByElem($p.get(0))
          }
        })
      }

      // enter时，不能使用 div 换行
      Txt.fn.bindEnterForDiv = function() {
        var tags = E.config.legalTags // 配置中编辑器要求的合法标签，如 p head table blockquote ul ol 等
        var self = this
        var editor = self.editor
        var $txt = self.$txt

        var $keydownDivElem
        function divHandler() {
          if (!$keydownDivElem) {
            return
          }

          var $pElem = $('<p>' + $keydownDivElem.html() + '</p>')
          $keydownDivElem.after($pElem)
          $keydownDivElem.remove()
        }

        $txt.on('keydown keyup', function(e) {
          if (e.keyCode !== 13) {
            return
          }
          // 查找合法标签
          var rangeElem = editor.getRangeElem()
          var targetElem = editor.getLegalTags(rangeElem)
          var $targetElem
          var $pElem

          if (!targetElem) {
            // 没找到合法标签，就去查找 div
            targetElem = editor.getSelfOrParentByName(rangeElem, 'div')
            if (!targetElem) {
              return
            }
            $targetElem = $(targetElem)

            if (e.type === 'keydown') {
              // 异步执行（同步执行会出现问题）
              $keydownDivElem = $targetElem
              setTimeout(divHandler, 0)
            }

            if (e.type === 'keyup') {
              // 将 div 的内容移动到 p 里面，并移除 div
              $pElem = $('<p>' + $targetElem.html() + '</p>')
              $targetElem.after($pElem)
              $targetElem.remove()

              // 如果是回车结束，将选区定位到行首
              editor.restoreSelectionByElem($pElem.get(0), 'start')
            }
          }
        })
      }

      // enter时，用 p 包裹 text
      Txt.fn.bindEnterForText = function() {
        var self = this
        var $txt = self.$txt
        var handle
        $txt.on('keyup', function(e) {
          if (e.keyCode !== 13) {
            return
          }
          if (!handle) {
            handle = function() {
              self.wrapImgAndText()
            }
          }
          setTimeout(handle)
        })
      }

      // tab 时，插入4个空格
      Txt.fn.bindTabEvent = function() {
        var self = this
        var editor = self.editor
        var $txt = self.$txt

        $txt.on('keydown', function(e) {
          if (e.keyCode !== 9) {
            // 只监听 tab 按钮
            return
          }
          // 如果浏览器支持 insertHtml 则插入4个空格。如果不支持，就不管了
          if (editor.queryCommandSupported('insertHtml')) {
            editor.command(e, 'insertHtml', '&nbsp;&nbsp;&nbsp;&nbsp;')
          }
        })
      }

      // 处理粘贴内容
      Txt.fn.bindPasteFilter = function() {
        var self = this
        var editor = self.editor
        var resultHtml = '' //存储最终的结果
        var $txt = self.$txt
        var legalTags = editor.config.legalTags
        var legalTagArr = legalTags.split(',')

        $txt.on('paste', function(e) {
          if (!editor.config.pasteFilter) {
            // 配置中取消了粘贴过滤
            return
          }

          var currentNodeName = editor.getRangeElem().nodeName
          if (currentNodeName === 'TD' || currentNodeName === 'TH') {
            // 在表格的单元格中粘贴，忽略所有内容。否则会出现异常情况
            return
          }

          resultHtml = '' // 先清空 resultHtml

          var pasteHtml, $paste, docSplitHtml
          var data = e.clipboardData || e.originalEvent.clipboardData
          var ieData = window.clipboardData

          if (editor.config.pasteText) {
            // 只粘贴纯文本

            if (data && data.getData) {
              // w3c
              pasteHtml = data.getData('text/plain')
            } else if (ieData && ieData.getData) {
              // IE
              pasteHtml = ieData.getData('text')
            } else {
              // 其他情况
              return
            }

            // 拼接为 <p> 标签
            if (pasteHtml) {
              resultHtml = '<p>' + pasteHtml + '</p>'
            }
          } else {
            // 粘贴过滤了样式的、只有标签的 html

            if (data && data.getData) {
              // w3c

              // 获取粘贴过来的html
              pasteHtml = data.getData('text/html')

              // 过滤从 word excel 粘贴过来的乱码
              docSplitHtml = pasteHtml.split('</html>')
              if (docSplitHtml.length === 2) {
                pasteHtml = docSplitHtml[0]
              }

              if (pasteHtml) {
                // 创建dom
                $paste = $('<div>' + pasteHtml + '</div>')
                // 处理，并将结果存储到 resultHtml 『全局』变量
                handle($paste.get(0))
              } else {
                // 得不到html，试图获取text
                pasteHtml = data.getData('text/plain')
                if (pasteHtml) {
                  // 替换特殊字符
                  pasteHtml = pasteHtml
                    .replace(/[ ]/g, '&nbsp;')
                    .replace(/</g, '&lt;')
                    .replace(/>/g, '&gt;')
                    .replace(/\n/g, '</p><p>')
                  // 拼接
                  resultHtml = '<p>' + pasteHtml + '</p>'

                  // 查询链接
                  resultHtml = resultHtml.replace(
                    /<p>(https?:\/\/.*?)<\/p>/gi,
                    function(match, link) {
                      return (
                        '<p><a href="' +
                        link +
                        '" target="_blank">' +
                        link +
                        '</p>'
                      )
                    }
                  )
                }
              }
            } else if (ieData && ieData.getData) {
              // IE 直接从剪切板中取出纯文本格式
              resultHtml = ieData.getData('text')
              if (!resultHtml) {
                return
              }
              // 拼接为 <p> 标签
              resultHtml = '<p>' + resultHtml + '</p>'
              resultHtml = resultHtml.replace(new RegExp('\n', 'g'), '</p><p>')
            } else {
              // 其他情况
              return
            }
          }

          // 执行命令
          if (resultHtml) {
            editor.command(e, 'insertHtml', resultHtml)

            // 删除内容为空的 p 和嵌套的 p
            self.clearEmptyOrNestP()
          }
        })

        // 处理粘贴的内容
        function handle(elem) {
          if (!elem || !elem.nodeType || !elem.nodeName) {
            return
          }
          var $elem
          var nodeName = elem.nodeName.toLowerCase()
          var nodeType = elem.nodeType
          var childNodesClone

          // 只处理文本和普通node标签
          if (nodeType !== 3 && nodeType !== 1) {
            return
          }

          $elem = $(elem)

          // 如果是容器，则继续深度遍历
          if (nodeName === 'div') {
            childNodesClone = []
            $.each(elem.childNodes, function(index, item) {
              // elem.childNodes 可获取TEXT节点，而 $elem.children() 就获取不到
              // 先将 elem.childNodes 拷贝一份，一面在循环递归过程中 elem 发生变化
              childNodesClone.push(item)
            })
            // 遍历子元素，执行操作
            $.each(childNodesClone, function() {
              handle(this)
            })
            return
          }

          if (legalTagArr.indexOf(nodeName) >= 0) {
            // 如果是合法标签之内的，则根据元素类型，获取值
            resultHtml += getResult(elem)
          } else if (nodeType === 3) {
            // 如果是文本，则直接插入 p 标签
            resultHtml += '<p>' + elem.textContent + '</p>'
          } else if (nodeName === 'br') {
            // <br>保留
            resultHtml += '<br/>'
          } else {
            // 忽略的标签
            if (
              [
                'meta',
                'style',
                'script',
                'object',
                'form',
                'iframe',
                'hr'
              ].indexOf(nodeName) >= 0
            ) {
              return
            }
            // 其他标签，移除属性，插入 p 标签
            $elem = $(removeAttrs(elem))
            // 注意，这里的 clone() 是必须的，否则会出错
            resultHtml += $('<div>')
              .append($elem.clone())
              .html()
          }
        }

        // 获取元素的结果
        function getResult(elem) {
          var nodeName = elem.nodeName.toLowerCase()
          var $elem
          var htmlForP = ''
          var htmlForLi = ''

          if (['blockquote'].indexOf(nodeName) >= 0) {
            // 直接取出元素text即可
            $elem = $(elem)
            return '<' + nodeName + '>' + $elem.text() + '</' + nodeName + '>'
          } else if (
            ['p', 'h1', 'h2', 'h3', 'h4', 'h5'].indexOf(nodeName) >= 0
          ) {
            //p head 取出 text 和链接
            elem = removeAttrs(elem)
            $elem = $(elem)
            htmlForP = $elem.html()

            // 剔除 a img 之外的元素
            htmlForP = htmlForP.replace(/<.*?>/gi, function(tag) {
              if (
                tag === '</a>' ||
                tag.indexOf('<a ') === 0 ||
                tag.indexOf('<img ') === 0
              ) {
                return tag
              } else {
                return ''
              }
            })

            return '<' + nodeName + '>' + htmlForP + '</' + nodeName + '>'
          } else if (['ul', 'ol'].indexOf(nodeName) >= 0) {
            // ul ol元素，获取子元素（li元素）的text link img，再拼接
            $elem = $(elem)
            $elem.children().each(function() {
              var $li = $(removeAttrs(this))
              var html = $li.html()

              html = html.replace(/<.*?>/gi, function(tag) {
                if (
                  tag === '</a>' ||
                  tag.indexOf('<a ') === 0 ||
                  tag.indexOf('<img ') === 0
                ) {
                  return tag
                } else {
                  return ''
                }
              })

              htmlForLi += '<li>' + html + '</li>'
            })
            return '<' + nodeName + '>' + htmlForLi + '</' + nodeName + '>'
          } else {
            // 其他元素，移除元素属性
            $elem = $(removeAttrs(elem))
            return $('<div>')
              .append($elem)
              .html()
          }
        }

        // 移除一个元素（子元素）的attr
        function removeAttrs(elem) {
          var attrs = elem.attributes || []
          var attrNames = []
          var exception = ['href', 'target', 'src', 'alt', 'rowspan', 'colspan'] //例外情况

          // 先存储下elem中所有 attr 的名称
          $.each(attrs, function(key, attr) {
            if (attr && attr.nodeType === 2) {
              attrNames.push(attr.nodeName)
            }
          })
          // 再根据名称删除所有attr
          $.each(attrNames, function(key, attr) {
            if (exception.indexOf(attr) < 0) {
              // 除了 exception 规定的例外情况，删除其他属性
              elem.removeAttribute(attr)
            }
          })

          // 递归子节点
          var children = elem.childNodes
          if (children.length) {
            $.each(children, function(key, value) {
              removeAttrs(value)
            })
          }

          return elem
        }
      }

      // 绑定 $txt.formatText() 方法
      Txt.fn.bindFormatText = function() {
        var self = this
        var editor = self.editor
        var $txt = self.$txt
        var legalTags = E.config.legalTags
        var legalTagArr = legalTags.split(',')
        var length = legalTagArr.length
        var regArr = []

        // 将 E.config.legalTags 配置的有效字符，生成正则表达式
        $.each(legalTagArr, function(k, tag) {
          var reg = '>\\s*<(' + tag + ')>'
          regArr.push(new RegExp(reg, 'ig'))
        })

        // 增加 li
        regArr.push(new RegExp('>\\s*<(li)>', 'ig'))

        // 增加 tr
        regArr.push(new RegExp('>\\s*<(tr)>', 'ig'))

        // 增加 code
        regArr.push(new RegExp('>\\s*<(code)>', 'ig'))

        // 生成 formatText 方法
        $txt.formatText = function() {
          var $temp = $('<div>')
          var html = $txt.html()

          // 去除空格
          html = html.replace(/\s*</gi, '<')

          // 段落、表格之间换行
          $.each(regArr, function(k, reg) {
            if (!reg.test(html)) {
              return
            }
            html = html.replace(reg, function(matchStr, tag) {
              return '>\n<' + tag + '>'
            })
          })

          $temp.html(html)
          return $temp.text()
        }
      }

      // 定制 $txt.html 方法
      Txt.fn.bindHtml = function() {
        var self = this
        var editor = self.editor
        var $txt = self.$txt
        var $valueContainer = editor.$valueContainer
        var valueNodeName = editor.valueNodeName

        $txt.html = function(html) {
          var result

          if (valueNodeName === 'div') {
            // div 生成的编辑器，取值、赋值，都直接触发jquery的html方法
            result = $.fn.html.call($txt, html)
          }

          // textarea 生成的编辑器，则需要考虑赋值时，也给textarea赋值

          if (html === undefined) {
            // 取值，直接触发jquery原生html方法
            result = $.fn.html.call($txt)

            // 替换 html 中，src和href属性中的 & 字符。
            // 因为 .html() 或者 .innerHTML 会把所有的 & 字符都改成 &amp; 但是 src 和 href 中的要保持 &
            result = result.replace(/(href|src)\=\"(.*)\"/gim, function(
              a,
              b,
              c
            ) {
              return b + '="' + c.replace('&amp;', '&') + '"'
            })
          } else {
            // 赋值，需要同时给 textarea 赋值
            result = $.fn.html.call($txt, html)
            $valueContainer.val(html)
          }

          if (html === undefined) {
            return result
          } else {
            // 手动触发 change 事件，因为 $txt 监控了 change 事件来判断是否需要执行 editor.onchange
            $txt.change()
          }
        }
      }
    })
    // Txt.fn api
    _e(function(E, $) {
      var Txt = E.Txt

      var txtChangeEventNames = 'propertychange change click keyup input paste'

      // 渲染
      Txt.fn.render = function() {
        var $txt = this.$txt
        var $editorContainer = this.editor.$editorContainer
        $editorContainer.append($txt)
      }

      // 计算高度
      Txt.fn.initHeight = function() {
        var editor = this.editor
        var $txt = this.$txt
        var valueContainerHeight = editor.$valueContainer.height()
        var menuHeight = editor.menuContainer.height()
        var txtHeight = valueContainerHeight - menuHeight

        // 限制最小为 50px
        txtHeight = txtHeight < 50 ? 50 : txtHeight

        $txt.height(txtHeight)

        // 记录原始高度
        editor.valueContainerHeight = valueContainerHeight

        // 设置 max-height
        this.initMaxHeight(txtHeight, menuHeight)
      }

      // 计算最大高度
      Txt.fn.initMaxHeight = function(txtHeight, menuHeight) {
        var editor = this.editor
        var $menuContainer = editor.menuContainer.$menuContainer
        var $txt = this.$txt
        var $wrap = $('<div>')

        // 需要浏览器支持 max-height，否则不管
        if (
          window.getComputedStyle &&
          'max-height' in window.getComputedStyle($txt.get(0))
        ) {
          // 获取 max-height 并判断是否有值
          var maxHeight = parseInt(editor.$valueContainer.css('max-height'))
          if (isNaN(maxHeight)) {
            return
          }

          // max-height 和『全屏』暂时有冲突
          if (editor.menus.fullscreen) {
            E.warn(
              'max-height和『全屏』菜单一起使用时，会有一些问题尚未解决，请暂时不要两个同时使用'
            )
            return
          }

          // 标记
          editor.useMaxHeight = true

          // 设置maxheight
          $wrap.css({
            'max-height': maxHeight - menuHeight + 'px',
            'overflow-y': 'auto'
          })
          $txt.css({
            height: 'auto',
            'overflow-y': 'visible',
            'min-height': txtHeight + 'px'
          })

          // 滚动式，菜单阴影
          $wrap.on('scroll', function() {
            if ($txt.parent().scrollTop() > 10) {
              $menuContainer.addClass('wangEditor-menu-shadow')
            } else {
              $menuContainer.removeClass('wangEditor-menu-shadow')
            }
          })

          // 需在编辑器区域外面再包裹一层
          $txt.wrap($wrap)
        }
      }

      // 保存选区
      Txt.fn.saveSelectionEvent = function() {
        var $txt = this.$txt
        var editor = this.editor
        var timeoutId
        var dt = Date.now()

        function save() {
          editor.saveSelection()
        }

        // 同步保存选区
        function saveSync() {
          // 100ms之内，不重复保存
          if (Date.now() - dt < 100) {
            return
          }

          dt = Date.now()
          save()
        }

        // 异步保存选区
        function saveAync() {
          // 节流，防止高频率重复操作
          if (timeoutId) {
            clearTimeout(timeoutId)
          }
          timeoutId = setTimeout(save, 300)
        }

        // txt change 、focus、blur 时随时保存选区
        $txt.on(txtChangeEventNames + ' focus blur', function(e) {
          // 先同步保存选区，为了让接下来就马上要执行 editor.getRangeElem() 的程序
          // 能够获取到正确的 rangeElem
          saveSync()

          // 再异步保存选区，为了确定更加准确的选区，为后续的操作做准备
          saveAync()
        })

        // 鼠标拖拽选择时，可能会拖拽到编辑器区域外面再松手，此时 $txt 就监听不到 click事件了
        $txt
          .on('mousedown', function() {
            $txt.on('mouseleave.saveSelection', function(e) {
              // 先同步后异步，如上述注释
              saveSync()
              saveAync()

              // 顺道吧菜单状态也更新了
              editor.updateMenuStyle()
            })
          })
          .on('mouseup', function() {
            $txt.off('mouseleave.saveSelection')
          })
      }

      // 随时更新 value
      Txt.fn.updateValueEvent = function() {
        var $txt = this.$txt
        var editor = this.editor
        var timeoutId, oldValue

        // 触发 onchange 事件
        function doOnchange() {
          var val = $txt.html()
          if (oldValue === val) {
            // 无变化
            return
          }

          // 触发 onchange 事件
          if (editor.onchange && typeof editor.onchange === 'function') {
            editor.onchange.call(editor)
          }

          // 更新内容
          editor.updateValue()

          // 记录最新内容
          oldValue = val
        }

        // txt change 时随时更新内容
        $txt.on(txtChangeEventNames, function(e) {
          // 初始化
          if (oldValue == null) {
            oldValue = $txt.html()
          }

          // 监控内容变化（停止操作 100ms 之后立即执行）
          if (timeoutId) {
            clearTimeout(timeoutId)
          }
          timeoutId = setTimeout(doOnchange, 100)
        })
      }

      // 随时更新 menustyle
      Txt.fn.updateMenuStyleEvent = function() {
        var $txt = this.$txt
        var editor = this.editor

        // txt change 时随时更新内容
        $txt.on(txtChangeEventNames, function(e) {
          editor.updateMenuStyle()
        })
      }

      // 最后插入试图插入 <p><br><p>
      Txt.fn.insertEmptyP = function() {
        var $txt = this.$txt
        var $children = $txt.children()

        if ($children.length === 0) {
          $txt.append($('<p><br></p>'))
          return
        }

        if ($.trim($children.last().html()).toLowerCase() !== '<br>') {
          $txt.append($('<p><br></p>'))
        }
      }

      // 将编辑器暴露出来的文字和图片，都用 p 来包裹
      Txt.fn.wrapImgAndText = function() {
        var $txt = this.$txt
        var $imgs = $txt.children('img')
        var txt = $txt[0]
        var childNodes = txt.childNodes
        var childrenLength = childNodes.length
        var i, childNode, p

        // 处理图片
        $imgs.length &&
          $imgs.each(function() {
            $(this).wrap('<p>')
          })

        // 处理文字
        for (i = 0; i < childrenLength; i++) {
          childNode = childNodes[i]
          if (
            childNode.nodeType === 3 &&
            childNode.textContent &&
            $.trim(childNode.textContent)
          ) {
            $(childNode).wrap('<p>')
          }
        }
      }

      // 清空内容为空的<p>，以及重复包裹的<p>（在windows下的chrome粘贴文字之后，会出现上述情况）
      Txt.fn.clearEmptyOrNestP = function() {
        var $txt = this.$txt
        var $pList = $txt.find('p')

        $pList.each(function() {
          var $p = $(this)
          var $children = $p.children()
          var childrenLength = $children.length
          var $firstChild
          var content = $.trim($p.html())

          // 内容为空的p
          if (!content) {
            $p.remove()
            return
          }

          // 嵌套的p
          if (childrenLength === 1) {
            $firstChild = $children.first()
            if ($firstChild.get(0) && $firstChild.get(0).nodeName === 'P') {
              $p.html($firstChild.html())
            }
          }
        })
      }

      // 获取 scrollTop
      Txt.fn.scrollTop = function(val) {
        var self = this
        var editor = self.editor
        var $txt = self.$txt

        if (editor.useMaxHeight) {
          return $txt.parent().scrollTop(val)
        } else {
          return $txt.scrollTop(val)
        }
      }

      // 鼠标hover时候，显示p、head的高度
      Txt.fn.showHeightOnHover = function() {
        var editor = this.editor
        var $editorContainer = editor.$editorContainer
        var menuContainer = editor.menuContainer
        var $txt = this.$txt
        var $tip = $('<i class="height-tip"><i>')
        var isTipInTxt = false

        function addAndShowTip($target) {
          if (!isTipInTxt) {
            $editorContainer.append($tip)
            isTipInTxt = true
          }

          var txtTop = $txt.position().top
          var txtHeight = $txt.outerHeight()

          var height = $target.height()
          var top = $target.position().top
          var marginTop = parseInt($target.css('margin-top'), 10)
          var paddingTop = parseInt($target.css('padding-top'), 10)
          var marginBottom = parseInt($target.css('margin-bottom'), 10)
          var paddingBottom = parseInt($target.css('padding-bottom'), 10)

          // 计算初步的结果
          var resultHeight =
            height + paddingTop + marginTop + paddingBottom + marginBottom
          var resultTop = top + menuContainer.height()

          // var spaceValue;

          // // 判断是否超出下边界
          // spaceValue = (resultTop + resultHeight) - (txtTop + txtHeight);
          // if (spaceValue > 0) {
          //     resultHeight = resultHeight - spaceValue;
          // }

          // // 判断是否超出了下边界
          // spaceValue = txtTop > resultTop;
          // if (spaceValue) {
          //     resultHeight = resultHeight - spaceValue;
          //     top = top + spaceValue;
          // }

          // 按照最终结果渲染
          $tip.css({
            height:
              height + paddingTop + marginTop + paddingBottom + marginBottom,
            top: top + menuContainer.height()
          })
        }
        function removeTip() {
          if (!isTipInTxt) {
            return
          }
          $tip.remove()
          isTipInTxt = false
        }

        $txt
          .on(
            'mouseenter',
            'ul,ol,blockquote,p,h1,h2,h3,h4,h5,table,pre',
            function(e) {
              addAndShowTip($(e.currentTarget))
            }
          )
          .on('mouseleave', function() {
            removeTip()
          })
      }
    })
    // 工具函数
    _e(function(E, $) {
      // IE8 [].indexOf()
      if (!Array.prototype.indexOf) {
        //IE低版本不支持 arr.indexOf
        Array.prototype.indexOf = function(elem) {
          var i = 0,
            length = this.length
          for (; i < length; i++) {
            if (this[i] === elem) {
              return i
            }
          }
          return -1
        }
        //IE低版本不支持 arr.lastIndexOf
        Array.prototype.lastIndexOf = function(elem) {
          var length = this.length
          for (length = length - 1; length >= 0; length--) {
            if (this[length] === elem) {
              return length
            }
          }
          return -1
        }
      }

      // IE8 Date.now()
      if (!Date.now) {
        Date.now = function() {
          return new Date().valueOf()
        }
      }

      // console.log && console.warn && console.error
      var console = window.console
      var emptyFn = function() {}
      $.each(['info', 'log', 'warn', 'error'], function(key, value) {
        if (console == null) {
          E[value] = emptyFn
        } else {
          var _tt = this
          E[value] = function(info) {
            // 通过配置来控制打印输出
            if (E.config && E.config.printLog) {
              console[value]('wangEditor提示: ' + info)
            }
          }
        }
      })

      // 获取随机数
      E.random = function() {
        return Math.random()
          .toString()
          .slice(2)
      }

      // 浏览器是否支持 placeholder
      E.placeholder = 'placeholder' in document.createElement('input')

      // 兼容IE8的 input placeholder
      E.placeholderForIE8 = function($container) {
        if (E.placeholder) {
          return
        }
        $container.find('input[placeholder]').each(function() {
          var $input = $(this)
          var placeholder = $input.attr('placeholder')

          if ($input.val() === '') {
            $input.css('color', '#666')
            $input.val(placeholder)

            $input.on('focus.placeholder click.placeholder', function() {
              $input.val('')
              $input.css('color', '#333')
              $input.off('focus.placeholder click.placeholder')
            })
          }
        })
      }
    })
    // 语言包
    _e(function(E, $) {
      E.langs = {}

      // 中文
      E.langs['zh-cn'] = {
        bold: '粗体',
        underline: '下划线',
        italic: '斜体',
        forecolor: '文字颜色',
        bgcolor: '背景色',
        strikethrough: '删除线',
        eraser: '清空格式',
        source: '源码',
        quote: '引用',
        fontfamily: '字体',
        fontsize: '字号',
        head: '标题',
        orderlist: '有序列表',
        unorderlist: '无序列表',
        alignleft: '左对齐',
        aligncenter: '居中',
        alignright: '右对齐',
        link: '链接',
        linkTarget: '打开方式',
        text: '文本',
        submit: '提交',
        cancel: '取消',
        unlink: '取消链接',
        table: '表格',
        rows: '行',
        columns: '列',
        emotion: '表情',
        img: '图片',
        uploadImg: '上传图片',
        linkImg: '网络图片',
        video: '视频',
        width: '宽',
        height: '高',
        location: '位置',
        loading: '加载中',
        searchlocation: '搜索位置',
        dynamicMap: '动态地图',
        clearLocation: '清除位置',
        langDynamicOneLocation: '动态地图只能显示一个位置',
        insertcode: '插入代码',
        undo: '撤销',
        redo: '重复',
        fullscreen: '全屏',
        openLink: '打开链接',
        uploadPlaceTxt: '上传中__',
        uploadTimeoutPlaceTxt: '上传超时__',
        uploadErrorPlaceTxt: '上传错误__'
      }

      // 英文
      E.langs.en = {
        bold: 'Bold',
        underline: 'Underline',
        italic: 'Italic',
        forecolor: 'Color',
        bgcolor: 'Backcolor',
        strikethrough: 'Strikethrough',
        eraser: 'Eraser',
        source: 'Codeview',
        quote: 'Quote',
        fontfamily: 'Font family',
        fontsize: 'Font size',
        head: 'Head',
        orderlist: 'Ordered list',
        unorderlist: 'Unordered list',
        alignleft: 'Align left',
        aligncenter: 'Align center',
        alignright: 'Align right',
        link: 'Insert link',
        linkTarget: 'Open mode',
        text: 'Text',
        submit: 'Submit',
        cancel: 'Cancel',
        unlink: 'Unlink',
        table: 'Table',
        rows: 'rows',
        columns: 'columns',
        emotion: 'Emotions',
        img: 'Image',
        uploadImg: 'Upload',
        linkImg: 'Link',
        video: 'Video',
        width: 'width',
        height: 'height',
        location: 'Location',
        loading: 'Loading',
        searchlocation: 'search',
        dynamicMap: 'Dynamic',
        clearLocation: 'Clear',
        langDynamicOneLocation: 'Only one location in dynamic map',
        insertcode: 'Insert Code',
        undo: 'Undo',
        redo: 'Redo',
        fullscreen: 'Full screnn',
        openLink: 'open link',
        uploadPlaceTxt: 'uploading__',
        uploadTimeoutPlaceTxt: 'upload_timeout__',
        uploadErrorPlaceTxt: 'upload_error__'
      }
    })
    // 全局配置
    _e(function(E, $) {
      E.config = {}

      // 全屏时的 z-index
      E.config.zindex = 10000

      // 是否打印log
      E.config.printLog = true

      // 菜单吸顶：false - 不吸顶；number - 吸顶，值为top值
      E.config.menuFixed = 0

      // 编辑源码时，过滤 javascript
      E.config.jsFilter = true

      // 编辑器允许的标签
      E.config.legalTags = 'p,h1,h2,h3,h4,h5,h6,blockquote,table,ul,ol,pre'

      // 语言包
      E.config.lang = E.langs['zh-cn']

      // 菜单配置
      E.config.menus = [
        'source',
        '|',
        'bold',
        'underline',
        'italic',
        'strikethrough',
        'eraser',
        'forecolor',
        'bgcolor',
        '|',
        'quote',
        'fontfamily',
        'fontsize',
        'head',
        'unorderlist',
        'orderlist',
        'alignleft',
        'aligncenter',
        'alignright',
        '|',
        'link',
        'unlink',
        'table',
        'emotion',
        '|',
        'img',
        'video',
        'location',
        'insertcode',
        '|',
        'undo',
        'redo',
        'fullscreen'
      ]

      // 颜色配置
      E.config.colors = {
        // 'value': 'title'
        '#880000': '暗红色',
        '#800080': '紫色',
        '#ff0000': '红色',
        '#ff00ff': '鲜粉色',
        '#000080': '深蓝色',
        '#0000ff': '蓝色',
        '#00ffff': '湖蓝色',
        '#008080': '蓝绿色',
        '#008000': '绿色',
        '#808000': '橄榄色',
        '#00ff00': '浅绿色',
        '#ffcc00': '橙黄色',
        '#808080': '灰色',
        '#c0c0c0': '银色',
        '#000000': '黑色',
        '#ffffff': '白色'
      }

      // 字体
      E.config.familys = [
        '宋体',
        '黑体',
        '楷体',
        '微软雅黑',
        'Arial',
        'Verdana',
        'Georgia',
        'Times New Roman',
        'Microsoft JhengHei',
        'Trebuchet MS',
        'Courier New',
        'Impact',
        'Comic Sans MS',
        'Consolas'
      ]

      // 字号
      E.config.fontsizes = {
        // 格式：'value': 'title'
        1: '12px',
        2: '13px',
        3: '16px',
        4: '18px',
        5: '24px',
        6: '32px',
        7: '48px'
      }

      // 表情包
      E.config.emotionsShow = 'icon' // 显示项，默认为'icon'，也可以配置成'value'
      E.config.emotions = {
        // 'default': {
        //     title: '默认',
        //     data: './emotions.data'
        // },
        weibo: {
          title: '微博表情',
          data: [
            {
              icon:
                'http://img.t.sinajs.cn/t35/style/images/common/face/ext/normal/7a/shenshou_thumb.gif',
              value: '[草泥马]'
            },
            {
              icon:
                'http://img.t.sinajs.cn/t35/style/images/common/face/ext/normal/60/horse2_thumb.gif',
              value: '[神马]'
            },
            {
              icon:
                'http://img.t.sinajs.cn/t35/style/images/common/face/ext/normal/bc/fuyun_thumb.gif',
              value: '[浮云]'
            },
            {
              icon:
                'http://img.t.sinajs.cn/t35/style/images/common/face/ext/normal/c9/geili_thumb.gif',
              value: '[给力]'
            },
            {
              icon:
                'http://img.t.sinajs.cn/t35/style/images/common/face/ext/normal/f2/wg_thumb.gif',
              value: '[围观]'
            },
            {
              icon:
                'http://img.t.sinajs.cn/t35/style/images/common/face/ext/normal/70/vw_thumb.gif',
              value: '[威武]'
            },
            {
              icon:
                'http://img.t.sinajs.cn/t35/style/images/common/face/ext/normal/6e/panda_thumb.gif',
              value: '[熊猫]'
            },
            {
              icon:
                'http://img.t.sinajs.cn/t35/style/images/common/face/ext/normal/81/rabbit_thumb.gif',
              value: '[兔子]'
            },
            {
              icon:
                'http://img.t.sinajs.cn/t35/style/images/common/face/ext/normal/bc/otm_thumb.gif',
              value: '[奥特曼]'
            },
            {
              icon:
                'http://img.t.sinajs.cn/t35/style/images/common/face/ext/normal/15/j_thumb.gif',
              value: '[囧]'
            },
            {
              icon:
                'http://img.t.sinajs.cn/t35/style/images/common/face/ext/normal/89/hufen_thumb.gif',
              value: '[互粉]'
            },
            {
              icon:
                'http://img.t.sinajs.cn/t35/style/images/common/face/ext/normal/c4/liwu_thumb.gif',
              value: '[礼物]'
            },
            {
              icon:
                'http://img.t.sinajs.cn/t35/style/images/common/face/ext/normal/ac/smilea_thumb.gif',
              value: '[呵呵]'
            },
            {
              icon:
                'http://img.t.sinajs.cn/t35/style/images/common/face/ext/normal/0b/tootha_thumb.gif',
              value: '[哈哈]'
            }
          ]
        }
      }

      // 上传图片的配置
      // server地址
      E.config.uploadImgUrl = ''
      // 超时时间
      E.config.uploadTimeout = 20 * 1000
      // 用于存储上传回调事件
      E.config.uploadImgFns = {}
      // 自定义上传图片的filename
      // E.config.uploadImgFileName = 'customFileName';

      // 自定义上传，设置为 true 之后，显示上传图标
      E.config.customUpload = false
      // 自定义上传的init事件
      // E.config.customUploadInit = function () {....};
      E.config.uploadPlaceholderImg =
        ''
      // 自定义上传时传递的参数（如 token）
      E.config.uploadParams = {
        /* token: 'abcdef12345' */
      }

      // 自定义上传是的header参数
      E.config.uploadHeaders = {
        /* 'Accept' : 'text/x-json' */
      }

      // 跨域上传时传递 cookie，默认为 true
      E.config.withCredentials = true

      // 隐藏网络图片，默认为 false
      E.config.hideLinkImg = false

      // 是否过滤粘贴内容
      E.config.pasteFilter = true

      // 是否粘贴纯文本，当 editor.config.pasteFilter === false 时候，此配置将失效
      E.config.pasteText = false

      // 插入代码时，默认的语言
      E.config.codeDefaultLang = 'javascript'
    })
    // 全局UI
    _e(function(E, $) {
      E.UI = {}

      // 为菜单自定义配置的UI
      E.UI.menus = {
        // 这个 default 不加引号，在 IE8 会报错
        default: {
          normal:
            '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-command"></i></a>',
          selected: '.selected'
        },
        bold: {
          normal:
            '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-bold"></i></a>',
          selected: '.selected'
        },
        underline: {
          normal:
            '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-underline"></i></a>',
          selected: '.selected'
        },
        italic: {
          normal:
            '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-italic"></i></a>',
          selected: '.selected'
        },
        forecolor: {
          normal:
            '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-pencil"></i></a>',
          selected: '.selected'
        },
        bgcolor: {
          normal:
            '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-brush"></i></a>',
          selected: '.selected'
        },
        strikethrough: {
          normal:
            '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-strikethrough"></i></a>',
          selected: '.selected'
        },
        eraser: {
          normal:
            '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-eraser"></i></a>',
          selected: '.selected'
        },
        quote: {
          normal:
            '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-quotes-left"></i></a>',
          selected: '.selected'
        },
        source: {
          normal:
            '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-code"></i></a>',
          selected: '.selected'
        },
        fontfamily: {
          normal:
            '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-font2"></i></a>',
          selected: '.selected'
        },
        fontsize: {
          normal:
            '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-text-height"></i></a>',
          selected: '.selected'
        },
        head: {
          normal:
            '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-header"></i></a>',
          selected: '.selected'
        },
        orderlist: {
          normal:
            '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-list-numbered"></i></a>',
          selected: '.selected'
        },
        unorderlist: {
          normal:
            '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-list-bullet"></i></a>',
          selected: '.selected'
        },
        alignleft: {
          normal:
            '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-align-left"></i></a>',
          selected: '.selected'
        },
        aligncenter: {
          normal:
            '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-align-center"></i></a>',
          selected: '.selected'
        },
        alignright: {
          normal:
            '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-align-right"></i></a>',
          selected: '.selected'
        },
        link: {
          normal:
            '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-link"></i></a>',
          selected: '.selected'
        },
        unlink: {
          normal:
            '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-unlink"></i></a>',
          selected: '.selected'
        },
        table: {
          normal:
            '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-table"></i></a>',
          selected: '.selected'
        },
        emotion: {
          normal:
            '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-happy"></i></a>',
          selected: '.selected'
        },
        img: {
          normal:
            '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-picture"></i></a>',
          selected: '.selected'
        },
        video: {
          normal:
            '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-play"></i></a>',
          selected: '.selected'
        },
        location: {
          normal:
            '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-location"></i></a>',
          selected: '.selected'
        },
        insertcode: {
          normal:
            '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-terminal"></i></a>',
          selected: '.selected'
        },
        undo: {
          normal:
            '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-ccw"></i></a>',
          selected: '.selected'
        },
        redo: {
          normal:
            '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-cw"></i></a>',
          selected: '.selected'
        },
        fullscreen: {
          normal:
            '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-enlarge2"></i></a>',
          selected:
            '<a href="#" tabindex="-1" class="selected"><i class="wangeditor-menu-img-shrink2"></i></a>'
        }
      }
    })
    // 对象配置
    _e(function(E, $) {
      E.fn.initDefaultConfig = function() {
        var editor = this
        editor.config = $.extend({}, E.config)
        editor.UI = $.extend({}, E.UI)
      }
    })
    // 增加 container
    _e(function(E, $) {
      E.fn.addEditorContainer = function() {
        this.$editorContainer = $('<div class="wangEditor-container"></div>')
      }
    })
    // 增加编辑区域对象
    _e(function(E, $) {
      E.fn.addTxt = function() {
        var editor = this
        var txt = new E.Txt(editor)

        editor.txt = txt
      }
    })
    // 增加menuContainer对象
    _e(function(E, $) {
      E.fn.addMenuContainer = function() {
        var editor = this
        editor.menuContainer = new E.MenuContainer(editor)
      }
    })
    // 增加menus
    _e(function(E, $) {
      // 存储创建菜单的函数
      E.createMenuFns = []
      E.createMenu = function(fn) {
        E.createMenuFns.push(fn)
      }

      // 创建所有菜单
      E.fn.addMenus = function() {
        var editor = this
        var menuIds = editor.config.menus

        // 检验 menuId 是否在配置中存在
        function check(menuId) {
          if (menuIds.indexOf(menuId) >= 0) {
            return true
          }
          return false
        }

        // 遍历所有的菜单创建函数，并执行
        $.each(E.createMenuFns, function(k, createMenuFn) {
          createMenuFn.call(editor, check)
        })
      }
    })
    // bold菜单
    _e(function(E, $) {
      E.createMenu(function(check) {
        var menuId = 'bold'
        if (!check(menuId)) {
          return
        }

        var editor = this
        var lang = editor.config.lang

        // 创建 menu 对象
        var menu = new E.Menu({
          editor: editor,
          id: menuId,
          title: lang.bold,
          commandName: 'Bold'
        })

        // 定义选中状态下的click事件
        menu.clickEventSelected = function(e) {
          var isRangeEmpty = editor.isRangeEmpty()
          if (!isRangeEmpty) {
            // 如果选区有内容，则执行基础命令
            editor.command(e, 'Bold')
          } else {
            // 如果选区没有内容
            editor.commandForElem('b,strong,h1,h2,h3,h4,h5', e, 'Bold')
          }
        }

        // 增加到editor对象中
        editor.menus[menuId] = menu
      })
    })
    // underline菜单
    _e(function(E, $) {
      E.createMenu(function(check) {
        var menuId = 'underline'
        if (!check(menuId)) {
          return
        }

        var editor = this
        var lang = editor.config.lang

        // 创建 menu 对象
        var menu = new E.Menu({
          editor: editor,
          id: menuId,
          title: lang.underline,
          commandName: 'Underline'
        })

        // 定义选中状态下的click事件
        menu.clickEventSelected = function(e) {
          var isRangeEmpty = editor.isRangeEmpty()
          if (!isRangeEmpty) {
            // 如果选区有内容，则执行基础命令
            editor.command(e, 'Underline')
          } else {
            // 如果选区没有内容
            editor.commandForElem('u,a', e, 'Underline')
          }
        }

        // 增加到editor对象中
        editor.menus[menuId] = menu
      })
    })
    // italic 菜单
    _e(function(E, $) {
      E.createMenu(function(check) {
        var menuId = 'italic'
        if (!check(menuId)) {
          return
        }
        var editor = this
        var lang = editor.config.lang

        // 创建 menu 对象
        var menu = new E.Menu({
          editor: editor,
          id: menuId,
          title: lang.italic,
          commandName: 'Italic'
        })

        // 定义选中状态下的click事件
        menu.clickEventSelected = function(e) {
          var isRangeEmpty = editor.isRangeEmpty()
          if (!isRangeEmpty) {
            // 如果选区有内容，则执行基础命令
            editor.command(e, 'Italic')
          } else {
            // 如果选区没有内容
            editor.commandForElem('i', e, 'Italic')
          }
        }

        // 增加到editor对象中
        editor.menus[menuId] = menu
      })
    })
    // forecolor 菜单
    _e(function(E, $) {
      E.createMenu(function(check) {
        var menuId = 'forecolor'
        if (!check(menuId)) {
          return
        }
        var editor = this
        var lang = editor.config.lang
        var configColors = editor.config.colors

        // 创建 menu 对象
        var menu = new E.Menu({
          editor: editor,
          id: menuId,
          title: lang.forecolor
        })

        // 创建 dropPanel
        var $content = $('<div></div>')
        $.each(configColors, function(k, v) {
          $content.append(
            [
              '<a href="#" class="color-item"',
              '    title="' + v + '" commandValue="' + k + '" ',
              '    style="color: ' + k + '" ',
              '><i class="wangeditor-menu-img-pencil"></i></a>'
            ].join('')
          )
        })
        $content.on('click', 'a[commandValue]', function(e) {
          // 执行命令
          var $elem = $(this)
          var commandValue = $elem.attr('commandValue')

          if (menu.selected && editor.isRangeEmpty()) {
            // 当前处于选中状态，并且选中内容为空
            editor.commandForElem('font[color]', e, 'forecolor', commandValue)
          } else {
            // 当前未处于选中状态，或者有选中内容。则执行默认命令
            editor.command(e, 'forecolor', commandValue)
          }
        })
        menu.dropPanel = new E.DropPanel(editor, menu, {
          $content: $content,
          width: 125
        })

        // 定义 update selected 事件
        menu.updateSelectedEvent = function() {
          var rangeElem = editor.getRangeElem()
          rangeElem = editor.getSelfOrParentByName(rangeElem, 'font[color]')
          if (rangeElem) {
            return true
          }
          return false
        }

        // 增加到editor对象中
        editor.menus[menuId] = menu
      })
    })
    // bgcolor 菜单
    _e(function(E, $) {
      E.createMenu(function(check) {
        var menuId = 'bgcolor'
        if (!check(menuId)) {
          return
        }
        var editor = this
        var lang = editor.config.lang
        var configColors = editor.config.colors

        // 检查元素是否有 background-color: 内联样式
        function checkElemFn(elem) {
          var cssText
          if (elem && elem.style && elem.style.cssText != null) {
            cssText = elem.style.cssText
            if (cssText && cssText.indexOf('background-color:') >= 0) {
              return true
            }
          }
          return false
        }

        // 创建 menu 对象
        var menu = new E.Menu({
          editor: editor,
          id: menuId,
          title: lang.bgcolor
        })

        // 创建 dropPanel
        var $content = $('<div></div>')
        $.each(configColors, function(k, v) {
          $content.append(
            [
              '<a href="#" class="color-item"',
              '    title="' + v + '" commandValue="' + k + '" ',
              '    style="color: ' + k + '" ',
              '><i class="wangeditor-menu-img-brush"></i></a>'
            ].join('')
          )
        })
        $content.on('click', 'a[commandValue]', function(e) {
          // 执行命令

          var $elem = $(this)
          var commandValue = $elem.attr('commandValue')

          if (menu.selected && editor.isRangeEmpty()) {
            // 当前处于选中状态，并且选中内容为空。使用 commandForElem 执行命令
            editor.commandForElem(
              {
                selector: 'span,font',
                check: checkElemFn
              },
              e,
              'BackColor',
              commandValue
            )
          } else {
            // 当前未处于选中状态，或者有选中内容。则执行默认命令
            editor.command(e, 'BackColor', commandValue)
          }
        })
        menu.dropPanel = new E.DropPanel(editor, menu, {
          $content: $content,
          width: 125
        })

        // 定义 update selected 事件
        menu.updateSelectedEvent = function() {
          var rangeElem = editor.getRangeElem()
          rangeElem = editor.getSelfOrParentByName(
            rangeElem,
            'span,font',
            checkElemFn
          )

          if (rangeElem) {
            return true
          }
          return false
        }

        // 增加到editor对象中
        editor.menus[menuId] = menu
      })
    })
    // strikethrough 菜单
    _e(function(E, $) {
      E.createMenu(function(check) {
        var menuId = 'strikethrough'
        if (!check(menuId)) {
          return
        }
        var editor = this
        var lang = editor.config.lang

        // 创建 menu 对象
        var menu = new E.Menu({
          editor: editor,
          id: menuId,
          title: lang.strikethrough,
          commandName: 'StrikeThrough'
        })

        // 定义选中状态下的click事件
        menu.clickEventSelected = function(e) {
          var isRangeEmpty = editor.isRangeEmpty()
          if (!isRangeEmpty) {
            // 如果选区有内容，则执行基础命令
            editor.command(e, 'StrikeThrough')
          } else {
            // 如果选区没有内容
            editor.commandForElem('strike', e, 'StrikeThrough')
          }
        }

        // 增加到editor对象中
        editor.menus[menuId] = menu
      })
    })
    // eraser 菜单
    _e(function(E, $) {
      E.createMenu(function(check) {
        var menuId = 'eraser'
        if (!check(menuId)) {
          return
        }
        var editor = this
        var lang = editor.config.lang

        // 创建 menu 对象
        var menu = new E.Menu({
          editor: editor,
          id: menuId,
          title: lang.eraser,
          commandName: 'RemoveFormat'
        })

        // 定义点击事件
        menu.clickEvent = function(e) {
          var isRangeEmpty = editor.isRangeEmpty()

          if (!isRangeEmpty) {
            // 选区不是空的，则执行默认命令
            editor.command(e, 'RemoveFormat')
            return
          }

          var $clearElem

          // 自定义的命令函数
          function commandFn() {
            var editor = this
            var rangeElem
            var pElem, $pElem
            var quoteElem, $quoteElem
            var listElem, $listElem

            // 获取选区 elem
            rangeElem = editor.getRangeElem()
            // 第一步，获取 quote 父元素
            quoteElem = editor.getSelfOrParentByName(rangeElem, 'blockquote')
            if (quoteElem) {
              $quoteElem = $(quoteElem)
              $clearElem = $('<p>' + $quoteElem.text() + '</p>')
              $quoteElem.after($clearElem).remove()
            }
            // 第二步，获取 p h 父元素
            pElem = editor.getSelfOrParentByName(rangeElem, 'p,h1,h2,h3,h4,h5')
            if (pElem) {
              $pElem = $(pElem)
              $clearElem = $('<p>' + $pElem.text() + '</p>')
              $pElem.after($clearElem).remove()
            }
            // 第三步，获取list
            listElem = editor.getSelfOrParentByName(rangeElem, 'ul,ol')
            if (listElem) {
              $listElem = $(listElem)
              $clearElem = $('<p>' + $listElem.text() + '</p>')
              $listElem.after($clearElem).remove()
            }
          }

          // 自定义 callback 事件
          function callback() {
            // callback中，设置range为clearElem
            var editor = this
            if ($clearElem) {
              editor.restoreSelectionByElem($clearElem.get(0))
            }
          }

          // 执行自定义命令
          editor.customCommand(e, commandFn, callback)
        }

        // 增加到editor对象中
        editor.menus[menuId] = menu
      })
    })
    // source 菜单
    _e(function(E, $) {
      E.createMenu(function(check) {
        var menuId = 'source'
        if (!check(menuId)) {
          return
        }
        var editor = this
        var lang = editor.config.lang
        var txtHtml

        // 创建 menu 对象
        var menu = new E.Menu({
          editor: editor,
          id: menuId,
          title: lang.source
        })

        menu.isShowCode = false

        // 更新内容
        function updateValue() {
          var $code = menu.$codeTextarea
          var $txt = editor.txt.$txt
          var value = $.trim($code.val()) // 取值

          if (!value) {
            value = '<p><br></p>'
          }

          // 过滤js代码
          if (editor.config.jsFilter) {
            value = value.replace(/<script[\s\S]*?<\/script>/gi, '')
          }
          // 赋值
          try {
            $txt.html(value)
          } catch (ex) {
            // 更新 html 源码出错，一般都是取消了 js 过滤之后，js报错导致的
          }
        }

        // 定义click事件
        menu.clickEvent = function(e) {
          var self = this
          var editor = self.editor
          var $txt = editor.txt.$txt
          var txtOuterHeight = $txt.outerHeight()
          var txtHeight = $txt.height()

          if (!self.$codeTextarea) {
            self.$codeTextarea = $(
              '<textarea class="code-textarea"></textarea>'
            )
          }
          var $code = self.$codeTextarea
          $code.css({
            height: txtHeight,
            'margin-top': txtOuterHeight - txtHeight
          })

          // 赋值
          $code.val($txt.html())

          // 监控变化
          $code.on('change', function(e) {
            updateValue()
          })

          // 渲染
          $txt.after($code).hide()
          $code.show()

          // 更新状态
          menu.isShowCode = true

          // 执行 updateSelected 事件
          this.updateSelected()

          // 禁用其他菜单
          editor.disableMenusExcept('source')

          // 记录当前html值
          txtHtml = $txt.html()
        }

        // 定义选中状态下的click事件
        menu.clickEventSelected = function(e) {
          var self = this
          var editor = self.editor
          var $txt = editor.txt.$txt
          var $code = self.$codeTextarea
          var value

          if (!$code) {
            return
          }

          // 更新内容
          updateValue()

          // 渲染
          $code.after($txt).hide()
          $txt.show()

          // 更新状态
          menu.isShowCode = false

          // 执行 updateSelected 事件
          this.updateSelected()

          // 启用其他菜单
          editor.enableMenusExcept('source')

          // 判断是否执行 onchange 事件
          if ($txt.html() !== txtHtml) {
            if (editor.onchange && typeof editor.onchange === 'function') {
              editor.onchange.call(editor)
            }
          }
        }

        // 定义切换选中状态事件
        menu.updateSelectedEvent = function() {
          return this.isShowCode
        }

        // 增加到editor对象中
        editor.menus[menuId] = menu
      })
    })
    // quote 菜单
    _e(function(E, $) {
      E.createMenu(function(check) {
        var menuId = 'quote'
        if (!check(menuId)) {
          return
        }
        var editor = this
        var lang = editor.config.lang

        // 创建 menu 对象
        var menu = new E.Menu({
          editor: editor,
          id: menuId,
          title: lang.quote,
          commandName: 'formatBlock',
          commandValue: 'blockquote'
        })

        // 定义click事件
        menu.clickEvent = function(e) {
          var rangeElem = editor.getRangeElem()
          var $rangeElem
          if (!rangeElem) {
            e.preventDefault()
            return
          }
          var currentQuote = editor.getSelfOrParentByName(
            rangeElem,
            'blockquote'
          )
          var $quote

          if (currentQuote) {
            // 说明当前在quote之内，不做任何处理
            e.preventDefault()
            return
          }

          rangeElem = editor.getLegalTags(rangeElem)
          $rangeElem = $(rangeElem)

          // 无文字，则不允许执行引用
          if (!$rangeElem.text()) {
            return
          }

          if (!rangeElem) {
            // 执行默认命令
            // IE8 下执行此处（不过，经测试代码无效，也不报错）
            editor.command(e, 'formatBlock', 'blockquote')
            return
          }

          // 自定义command事件
          function commandFn() {
            $quote = $('<p>' + $rangeElem.text() + '</p>')
            $rangeElem.after($quote).remove()
            $quote.wrap('<blockquote>')
          }

          // 自定义 callback 事件
          function callback() {
            // callback中，设置range为quote
            var editor = this
            if ($quote) {
              editor.restoreSelectionByElem($quote.get(0))
            }
          }

          // 执行自定义命令
          editor.customCommand(e, commandFn, callback)
        }

        // 定义选中状态下的click事件
        menu.clickEventSelected = function(e) {
          var rangeElem
          var quoteElem
          var $lastChild

          // 获取当前选区的elem，并试图往上找 quote 元素
          rangeElem = editor.getRangeElem()
          quoteElem = editor.getSelfOrParentByName(rangeElem, 'blockquote')
          if (!quoteElem) {
            // 没找到，则返回
            e.preventDefault()
            return
          }

          // 自定义的command事件
          function commandFn() {
            var $quoteElem
            var $children

            $quoteElem = $(quoteElem)
            $children = $quoteElem.children()
            if ($children.length) {
              $children.each(function(k) {
                var $item = $(this)
                if ($item.get(0).nodeName === 'P') {
                  $quoteElem.after($item)
                } else {
                  $quoteElem.after('<p>' + $item.text() + '</p>')
                }
                $lastChild = $item // 记录最后一个子元素，用于callback中的range定位
              })
              $quoteElem.remove()
              return
            }
          }

          // 自定义的callback函数
          function callback() {
            // callback中，设置range为lastChild
            var editor = this
            if ($lastChild) {
              editor.restoreSelectionByElem($lastChild.get(0))
            }
          }

          // 执行自定义命令
          editor.customCommand(e, commandFn, callback)
        }

        // 定义更新选中状态的事件
        menu.updateSelectedEvent = function() {
          var self = this //菜单对象
          var editor = self.editor
          var rangeElem

          rangeElem = editor.getRangeElem()
          rangeElem = editor.getSelfOrParentByName(rangeElem, 'blockquote')

          if (rangeElem) {
            return true
          }

          return false
        }

        // 增加到editor对象中
        editor.menus[menuId] = menu

        // --------------- 两次点击 enter 跳出引用 ---------------
        editor.ready(function() {
          var editor = this
          var $txt = editor.txt.$txt
          var isPrevEnter = false // 是不是刚刚在quote中按了 enter 键
          $txt.on('keydown', function(e) {
            if (e.keyCode !== 13) {
              // 不是 enter 键
              isPrevEnter = false
              return
            }

            var rangeElem = editor.getRangeElem()
            rangeElem = editor.getSelfOrParentByName(rangeElem, 'blockquote')
            if (!rangeElem) {
              // 选区不是 quote
              isPrevEnter = false
              return
            }

            if (!isPrevEnter) {
              // 最近没有在qote中按enter键
              isPrevEnter = true
              return
            }

            var currentRangeElem = editor.getRangeElem()
            var $currentRangeElem = $(currentRangeElem)
            if ($currentRangeElem.length) {
              $currentRangeElem.parent().after($currentRangeElem)
            }

            // 设置选区
            editor.restoreSelectionByElem(currentRangeElem, 'start')

            isPrevEnter = false
            // 阻止默认行文
            e.preventDefault()
          })
        }) // editor.ready(

        // --------------- 处理quote中无内容时不能删除的问题 ---------------
        editor.ready(function() {
          var editor = this
          var $txt = editor.txt.$txt
          var $rangeElem

          function commandFn() {
            $rangeElem && $rangeElem.remove()
          }
          function callback() {
            if (!$rangeElem) {
              return
            }
            var $prev = $rangeElem.prev()
            if ($prev.length) {
              // 有 prev 则定位到 prev 最后
              editor.restoreSelectionByElem($prev.get(0))
            } else {
              // 无 prev 则初始化选区
              editor.initSelection()
            }
          }

          $txt.on('keydown', function(e) {
            if (e.keyCode !== 8) {
              // 不是 backspace 键
              return
            }

            var rangeElem = editor.getRangeElem()
            rangeElem = editor.getSelfOrParentByName(rangeElem, 'blockquote')
            if (!rangeElem) {
              // 选区不是 quote
              return
            }
            $rangeElem = $(rangeElem)

            var text = $rangeElem.text()
            if (text) {
              // quote 中还有内容
              return
            }
            editor.customCommand(e, commandFn, callback)
          }) // $txt.on
        }) // editor.ready(
      })
    })
    // 字体 菜单
    _e(function(E, $) {
      E.createMenu(function(check) {
        var menuId = 'fontfamily'
        if (!check(menuId)) {
          return
        }
        var editor = this
        var lang = editor.config.lang
        var configFamilys = editor.config.familys

        // 创建 menu 对象
        var menu = new E.Menu({
          editor: editor,
          id: menuId,
          title: lang.fontfamily,
          commandName: 'fontName'
        })

        // 初始化数据
        var data = {}
        /*
            data 需要的结构
            {
                'commandValue': 'title'
                ...
            }
        */
        $.each(configFamilys, function(k, v) {
          // configFamilys 是数组，data 是对象
          data[v] = v
        })

        // 创建droplist
        var tpl = '<span style="font-family:{#commandValue};">{#title}</span>'
        menu.dropList = new E.DropList(editor, menu, {
          data: data,
          tpl: tpl,
          selectorForELemCommand: 'font[face]' // 为了执行 editor.commandForElem 而传入的elem查询方式
        })

        // 定义 update selected 事件
        menu.updateSelectedEvent = function() {
          var rangeElem = editor.getRangeElem()
          rangeElem = editor.getSelfOrParentByName(rangeElem, 'font[face]')
          if (rangeElem) {
            return true
          }
          return false
        }

        // 增加到editor对象中
        editor.menus[menuId] = menu
      })
    })
    // 字号 菜单
    _e(function(E, $) {
      E.createMenu(function(check) {
        var menuId = 'fontsize'
        if (!check(menuId)) {
          return
        }
        var editor = this
        var lang = editor.config.lang
        var configSize = editor.config.fontsizes

        // 创建 menu 对象
        var menu = new E.Menu({
          editor: editor,
          id: menuId,
          title: lang.fontsize,
          commandName: 'fontSize'
        })

        // 初始化数据
        var data = configSize
        /*
            data 需要的结构
            {
                'commandValue': 'title'
                ...
            }
        */

        // 创建droplist
        var tpl = '<span style="font-size:{#title};">{#title}</span>'
        menu.dropList = new E.DropList(editor, menu, {
          data: data,
          tpl: tpl,
          selectorForELemCommand: 'font[size]' // 为了执行 editor.commandForElem 而传入的elem查询方式
        })

        // 定义 update selected 事件
        menu.updateSelectedEvent = function() {
          var rangeElem = editor.getRangeElem()
          rangeElem = editor.getSelfOrParentByName(rangeElem, 'font[size]')
          if (rangeElem) {
            return true
          }
          return false
        }

        // 增加到editor对象中
        editor.menus[menuId] = menu
      })
    })
    // head 菜单
    _e(function(E, $) {
      E.createMenu(function(check) {
        var menuId = 'head'
        if (!check(menuId)) {
          return
        }
        var editor = this
        var lang = editor.config.lang

        // 创建 menu 对象
        var menu = new E.Menu({
          editor: editor,
          id: menuId,
          title: lang.head,
          commandName: 'formatBlock'
        })

        // 初始化数据
        var data = {
          '<h1>': '标题1',
          '<h2>': '标题2',
          '<h3>': '标题3',
          '<h4>': '标题4',
          '<h5>': '标题5'
        }
        /*
            data 需要的结构
            {
                'commandValue': 'title'
                ...
            }
        */

        var isOrderedList
        function beforeEvent(e) {
          if (editor.queryCommandState('InsertOrderedList')) {
            isOrderedList = true

            // 先取消有序列表
            editor.command(e, 'InsertOrderedList')
          } else {
            isOrderedList = false
          }
        }

        function afterEvent(e) {
          if (isOrderedList) {
            // 再设置有序列表
            editor.command(e, 'InsertOrderedList')
          }
        }

        // 创建droplist
        var tpl = '{#commandValue}{#title}'
        menu.dropList = new E.DropList(editor, menu, {
          data: data,
          tpl: tpl,
          // 对 ol 直接设置 head，会出现每个 li 的 index 都变成 1 的问题，因此要先取消 ol，然后设置 head，最后再增加上 ol
          beforeEvent: beforeEvent,
          afterEvent: afterEvent
        })

        // 定义 update selected 事件
        menu.updateSelectedEvent = function() {
          var rangeElem = editor.getRangeElem()
          rangeElem = editor.getSelfOrParentByName(rangeElem, 'h1,h2,h3,h4,h5')
          if (rangeElem) {
            return true
          }
          return false
        }

        // 增加到editor对象中
        editor.menus[menuId] = menu
      })
    })
    // unorderlist 菜单
    _e(function(E, $) {
      E.createMenu(function(check) {
        var menuId = 'unorderlist'
        if (!check(menuId)) {
          return
        }
        var editor = this
        var lang = editor.config.lang

        // 创建 menu 对象
        var menu = new E.Menu({
          editor: editor,
          id: menuId,
          title: lang.unorderlist,
          commandName: 'InsertUnorderedList'
        })

        // 增加到editor对象中
        editor.menus[menuId] = menu
      })
    })
    // orderlist 菜单
    _e(function(E, $) {
      E.createMenu(function(check) {
        var menuId = 'orderlist'
        if (!check(menuId)) {
          return
        }
        var editor = this
        var lang = editor.config.lang

        // 创建 menu 对象
        var menu = new E.Menu({
          editor: editor,
          id: menuId,
          title: lang.orderlist,
          commandName: 'InsertOrderedList'
        })

        // 增加到editor对象中
        editor.menus[menuId] = menu
      })
    })
    // alignleft 菜单
    _e(function(E, $) {
      E.createMenu(function(check) {
        var menuId = 'alignleft'
        if (!check(menuId)) {
          return
        }
        var editor = this
        var lang = editor.config.lang

        // 创建 menu 对象
        var menu = new E.Menu({
          editor: editor,
          id: menuId,
          title: lang.alignleft,
          commandName: 'JustifyLeft'
        })

        // 定义 update selected 事件
        menu.updateSelectedEvent = function() {
          var rangeElem = editor.getRangeElem()
          rangeElem = editor.getSelfOrParentByName(
            rangeElem,
            'p,h1,h2,h3,h4,h5,li',
            function(elem) {
              var cssText
              if (elem && elem.style && elem.style.cssText != null) {
                cssText = elem.style.cssText
                if (cssText && /text-align:\s*left;/.test(cssText)) {
                  return true
                }
              }
              if ($(elem).attr('align') === 'left') {
                // ff 中，设置align-left之后，会是 <p align="left">xxx</p>
                return true
              }
              return false
            }
          )
          if (rangeElem) {
            return true
          }
          return false
        }

        // 增加到editor对象中
        editor.menus[menuId] = menu
      })
    })
    // aligncenter 菜单
    _e(function(E, $) {
      E.createMenu(function(check) {
        var menuId = 'aligncenter'
        if (!check(menuId)) {
          return
        }
        var editor = this
        var lang = editor.config.lang

        // 创建 menu 对象
        var menu = new E.Menu({
          editor: editor,
          id: menuId,
          title: lang.aligncenter,
          commandName: 'JustifyCenter'
        })

        // 定义 update selected 事件
        menu.updateSelectedEvent = function() {
          var rangeElem = editor.getRangeElem()
          rangeElem = editor.getSelfOrParentByName(
            rangeElem,
            'p,h1,h2,h3,h4,h5,li',
            function(elem) {
              var cssText
              if (elem && elem.style && elem.style.cssText != null) {
                cssText = elem.style.cssText
                if (cssText && /text-align:\s*center;/.test(cssText)) {
                  return true
                }
              }
              if ($(elem).attr('align') === 'center') {
                // ff 中，设置align-center之后，会是 <p align="center">xxx</p>
                return true
              }
              return false
            }
          )
          if (rangeElem) {
            return true
          }
          return false
        }

        // 增加到editor对象中
        editor.menus[menuId] = menu
      })
    })
    // alignright 菜单
    _e(function(E, $) {
      E.createMenu(function(check) {
        var menuId = 'alignright'
        if (!check(menuId)) {
          return
        }
        var editor = this
        var lang = editor.config.lang

        // 创建 menu 对象
        var menu = new E.Menu({
          editor: editor,
          id: menuId,
          title: lang.alignright,
          commandName: 'JustifyRight'
        })

        // 定义 update selected 事件
        menu.updateSelectedEvent = function() {
          var rangeElem = editor.getRangeElem()
          rangeElem = editor.getSelfOrParentByName(
            rangeElem,
            'p,h1,h2,h3,h4,h5,li',
            function(elem) {
              var cssText
              if (elem && elem.style && elem.style.cssText != null) {
                cssText = elem.style.cssText
                if (cssText && /text-align:\s*right;/.test(cssText)) {
                  return true
                }
              }
              if ($(elem).attr('align') === 'right') {
                // ff 中，设置align-right之后，会是 <p align="right">xxx</p>
                return true
              }
              return false
            }
          )
          if (rangeElem) {
            return true
          }
          return false
        }

        // 增加到editor对象中
        editor.menus[menuId] = menu
      })
    })
    // link 菜单
    _e(function(E, $) {
      E.createMenu(function(check) {
        var menuId = 'link'
        if (!check(menuId)) {
          return
        }
        var editor = this
        var lang = editor.config.lang

        // 创建 menu 对象
        var menu = new E.Menu({
          editor: editor,
          id: menuId,
          title: lang.link
        })

        // 创建 dropPanel
        var $content = $('<div></div>')
        var $div1 = $('<div style="margin:20px 10px;" class="clearfix"></div>')
        var $div2 = $div1.clone()
        var $div3 = $div1.clone().css('margin', '0 10px')
        var $div4 = $div1.clone().css('margin', '0 10px')
        var $textInput = $(
          '<input type="text" class="block" placeholder="' + lang.text + '"/>'
        )
        var $urlInput = $(
          '<input type="text" class="block" placeholder="' + lang.link + '"/>'
        )
        var $targetInput = $(
          '<input type="text" class="block" placeholder="' +
            lang.linkTarget +
            ': _self / _blank / _top / _parent" />'
        )
        var $btnSubmit = $('<button class="right">' + lang.submit + '</button>')
        var $btnCancel = $(
          '<button class="right gray">' + lang.cancel + '</button>'
        )

        $div1.append($textInput)
        $div2.append($urlInput)
        $div4.append($targetInput)
        $div4.append($btnSubmit).append($btnCancel)
        $content
          .append($div1)
          .append($div2)
          .append($div3)
          .append($div4)

        menu.dropPanel = new E.DropPanel(editor, menu, {
          $content: $content,
          width: 300
        })

        // 定义click事件
        menu.clickEvent = function(e) {
          var menu = this
          var dropPanel = menu.dropPanel

          // -------------隐藏----------------
          if (dropPanel.isShowing) {
            dropPanel.hide()
            return
          }

          // -------------显示----------------

          // 重置 input
          $textInput.val('')
          $urlInput.val('')
          $targetInput.val('')

          // 获取url
          var url = ''
          var rangeElem = editor.getRangeElem()
          rangeElem = editor.getSelfOrParentByName(rangeElem, 'a')
          if (
            rangeElem &&
            typeof rangeElem.getAttribute == 'function' &&
            rangeElem.getAttribute('href')
          ) {
            url = rangeElem.getAttribute('href') || ''
          } else if (rangeElem && !url) {
            url = rangeElem.href || ''
          }

          // 获取 text
          var text = ''
          var isRangeEmpty = editor.isRangeEmpty()
          if (!isRangeEmpty) {
            // 选区不是空
            text = editor.getRangeText() || ''
          } else if (rangeElem) {
            // 如果选区空，并且在 a 标签之内
            text = rangeElem.textContent || rangeElem.innerHTML
          }

          // 设置 url 和 text
          url && $urlInput.val(url)
          text && $textInput.val(text)

          // 如果有选区内容，textinput 不能修改
          if (!isRangeEmpty) {
            $textInput.attr('disabled', true)
          } else {
            $textInput.removeAttr('disabled')
          }

          // 显示（要设置好了所有input的值和属性之后再显示）
          dropPanel.show()
        }

        // 定义 update selected 事件
        menu.updateSelectedEvent = function() {
          var rangeElem = editor.getRangeElem()
          rangeElem = editor.getSelfOrParentByName(rangeElem, 'a')
          if (rangeElem) {
            return true
          }
          return false
        }

        // 『取消』 按钮
        $btnCancel.click(function(e) {
          e.preventDefault()
          menu.dropPanel.hide()
        })

        // 『确定』按钮
        $btnSubmit.click(function(e) {
          e.preventDefault()
          var rangeElem = editor.getRangeElem()
          var targetElem = editor.getSelfOrParentByName(rangeElem, 'a')
          var isRangeEmpty = editor.isRangeEmpty()

          var $linkElem, linkHtml
          var commandFn, callback
          var $txt = editor.txt.$txt
          var $oldLinks, $newLinks
          var uniqId = 'link' + E.random()

          // 获取数据
          var url = $.trim($urlInput.val())
          var text = $.trim($textInput.val())
          var target = $.trim($targetInput.val())
          if (!url) {
            menu.dropPanel.focusFirstInput()
            return
          }
          if (!text) {
            text = url
          }

          if (!isRangeEmpty) {
            // 选中区域有内容，则执行默认命令

            // 获取目前 txt 内所有链接，并为当前链接做一个标记
            $oldLinks = $txt.find('a')
            $oldLinks.attr(uniqId, '1')

            // 执行命令
            editor.command(e, 'createLink', url)

            // 去的没有标记的链接，即刚刚插入的链接
            $newLinks = $txt.find('a').not('[' + uniqId + ']')
            target && $newLinks.attr('target', target) // 增加 _blank
            // 去掉之前做的标记
            $oldLinks.removeAttr(uniqId)
          } else if (targetElem) {
            // 无选中区域，在 a 标签之内，修改该 a 标签的内容和链接
            $linkElem = $(targetElem)
            commandFn = function() {
              $linkElem.attr('href', url)
              $linkElem.text(text)
              target && $linkElem.attr('target', target)
            }
            callback = function() {
              var editor = this
              editor.restoreSelectionByElem(targetElem)
            }
            // 执行命令
            editor.customCommand(e, commandFn, callback)
          } else {
            // 无选中区域，不在 a 标签之内，插入新的链接
            linkHtml =
              '<a href="' +
              url +
              '"' +
              (target ? ' target="' + target + '"' : '') +
              '>' +
              text +
              '</a>'
            if (E.userAgent.indexOf('Firefox') > 0) {
              linkHtml += '<span>&nbsp;</span>'
            }
            editor.command(e, 'insertHtml', linkHtml)
          }
        })

        // 增加到editor对象中
        editor.menus[menuId] = menu
      })
    })
    // unlink 菜单
    _e(function(E, $) {
      E.createMenu(function(check) {
        var menuId = 'unlink'
        if (!check(menuId)) {
          return
        }
        var editor = this
        var lang = editor.config.lang

        // 创建 menu 对象
        var menu = new E.Menu({
          editor: editor,
          id: menuId,
          title: lang.unlink,
          commandName: 'unLink'
        })

        // click 事件
        menu.clickEvent = function(e) {
          var isRangeEmpty = editor.isRangeEmpty()
          if (!isRangeEmpty) {
            // 有选中区域，或者IE8，执行默认命令
            editor.command(e, 'unLink')
            return
          }

          // 无选中区域...

          var rangeElem = editor.getRangeElem()
          var aElem = editor.getSelfOrParentByName(rangeElem, 'a')
          if (!aElem) {
            // 不在 a 之内，返回
            e.preventDefault()
            return
          }

          // 在 a 之内
          var $a = $(aElem)
          var $span = $('<span>' + $a.text() + '</span>')
          function commandFn() {
            $a.after($span).remove()
          }
          function callback() {
            editor.restoreSelectionByElem($span.get(0))
          }
          editor.customCommand(e, commandFn, callback)
        }

        // 增加到editor对象中
        editor.menus[menuId] = menu
      })
    })
    // table 菜单
    _e(function(E, $) {
      E.createMenu(function(check) {
        var menuId = 'table'
        if (!check(menuId)) {
          return
        }
        var editor = this
        var lang = editor.config.lang

        // 创建 menu 对象
        var menu = new E.Menu({
          editor: editor,
          id: menuId,
          title: lang.table
        })

        // dropPanel 内容
        var $content = $(
          '<div style="font-size: 14px; color: #666; text-align:right;"></div>'
        )
        var $table = $(
          '<table class="choose-table" style="margin-bottom:10px;margin-top:5px;">'
        )
        var $row = $('<span>0</span>')
        var $rowspan = $('<span> '+lang.rows+' </span>')
        var $col = $('<span>0</span>')
        var $colspan = $('<span> '+lang.columns+'</span>')
        var $tr
        var i, j

        // 创建一个n行n列的表格
        for (i = 0; i < 15; i++) {
          $tr = $('<tr index="' + (i + 1) + '">')
          for (j = 0; j < 20; j++) {
            $tr.append($('<td index="' + (j + 1) + '">'))
          }
          $table.append($tr)
        }
        $content.append($table)
        $content
          .append($row)
          .append($rowspan)
          .append($col)
          .append($colspan)

        // 定义table事件
        $table
          .on('mouseenter', 'td', function(e) {
            var $currentTd = $(e.currentTarget)
            var currentTdIndex = $currentTd.attr('index')
            var $currentTr = $currentTd.parent()
            var currentTrIndex = $currentTr.attr('index')

            // 显示
            $row.text(currentTrIndex)
            $col.text(currentTdIndex)

            // 遍历设置背景颜色
            $table.find('tr').each(function() {
              var $tr = $(this)
              var trIndex = $tr.attr('index')
              if (parseInt(trIndex, 10) <= parseInt(currentTrIndex, 10)) {
                // 该行需要可能需要设置背景色
                $tr.find('td').each(function() {
                  var $td = $(this)
                  var tdIndex = $td.attr('index')
                  if (parseInt(tdIndex, 10) <= parseInt(currentTdIndex, 10)) {
                    // 需要设置背景色
                    $td.addClass('active')
                  } else {
                    // 需要移除背景色
                    $td.removeClass('active')
                  }
                })
              } else {
                // 改行不需要设置背景色
                $tr.find('td').removeClass('active')
              }
            })
          })
          .on('mouseleave', function(e) {
            // mouseleave 删除背景色
            $table.find('td').removeClass('active')

            $row.text(0)
            $col.text(0)
          })

        // 插入表格
        $table.on('click', 'td', function(e) {
          var $currentTd = $(e.currentTarget)
          var currentTdIndex = $currentTd.attr('index')
          var $currentTr = $currentTd.parent()
          var currentTrIndex = $currentTr.attr('index')

          var rownum = parseInt(currentTrIndex, 10)
          var colnum = parseInt(currentTdIndex, 10)

          // -------- 拼接tabel html --------

          var i, j
          var tableHtml = '<table>'
          for (i = 0; i < rownum; i++) {
            tableHtml += '<tr>'

            for (j = 0; j < colnum; j++) {
              tableHtml += '<td><span>&nbsp;</span></td>'
            }
            tableHtml += '</tr>'
          }
          tableHtml += '</table>'

          // -------- 执行命令 --------
          editor.command(e, 'insertHtml', tableHtml)
        })

        // 创建 panel
        menu.dropPanel = new E.DropPanel(editor, menu, {
          $content: $content,
          width: 262
        })

        // 增加到editor对象中
        editor.menus[menuId] = menu
      })
    })
    // emotion 菜单
    _e(function(E, $) {
      E.createMenu(function(check) {
        var menuId = 'emotion'
        if (!check(menuId)) {
          return
        }
        var editor = this
        var config = editor.config
        var lang = config.lang
        var configEmotions = config.emotions
        var emotionsShow = config.emotionsShow

        // 记录每一个表情图片的地址
        editor.emotionUrls = []

        // 创建 menu 对象
        var menu = new E.Menu({
          editor: editor,
          id: menuId,
          title: lang.emotion
        })

        // 添加表情图片的函数
        function insertEmotionImgs(data, $tabContent) {
          // 添加表情图片
          $.each(data, function(k, emotion) {
            var src = emotion.icon || emotion.url
            var value = emotion.value || emotion.title
            // 通过配置 editor.config.emotionsShow 的值来修改插入到编辑器的内容（图片/value）
            var commandValue = emotionsShow === 'icon' ? src : value
            var $command = $(
              '<a href="#" commandValue="' + commandValue + '"></a>'
            )
            var $img = $('<img>')
            $img.attr('_src', src) // 先将 src 复制到 '_src' 属性，先不加载

            $command.append($img)
            $tabContent.append($command)

            // 记录下每一个表情图片的地址
            editor.emotionUrls.push(src)
          })
        }

        // 拼接 dropPanel 内容
        var $panelContent = $('<div class="panel-tab"></div>')
        var $tabContainer = $('<div class="tab-container"></div>')
        var $contentContainer = $(
          '<div class="content-container emotion-content-container"></div>'
        )
        $.each(configEmotions, function(k, emotion) {
          var title = emotion.title
          var data = emotion.data

          E.log('正在处理 ' + title + ' 表情的数据...')

          // 增加该组表情的tab和content
          var $tab = $('<a href="#">' + title + ' </a>')
          $tabContainer.append($tab)
          var $tabContent = $('<div class="content"></div>')
          $contentContainer.append($tabContent)

          // tab 切换事件
          $tab.click(function(e) {
            $tabContainer.children().removeClass('selected')
            $contentContainer.children().removeClass('selected')
            $tabContent.addClass('selected')
            $tab.addClass('selected')
            e.preventDefault()
          })

          // 处理data
          if (typeof data === 'string') {
            // url 形式，需要通过ajax从该url获取数据
            E.log('将通过 ' + data + ' 地址ajax下载表情包')
            $.get(data, function(result) {
              result = $.parseJSON(result)
              E.log('下载完毕，得到 ' + result.length + ' 个表情')
              insertEmotionImgs(result, $tabContent)
            })
          } else if (
            Object.prototype.toString
              .call(data)
              .toLowerCase()
              .indexOf('array') > 0
          ) {
            // 数组，即 data 直接就是表情包数据
            insertEmotionImgs(data, $tabContent)
          } else {
            // 其他情况，data格式不对
            E.error(
              'data 数据格式错误，请修改为正确格式，参考文档：' + E.docsite
            )
            return
          }
        })
        $panelContent.append($tabContainer).append($contentContainer)

        // 默认显示第一个tab
        $tabContainer
          .children()
          .first()
          .addClass('selected')
        $contentContainer
          .children()
          .first()
          .addClass('selected')

        // 插入表情command事件
        $contentContainer.on('click', 'a[commandValue]', function(e) {
          var $a = $(e.currentTarget)
          var commandValue = $a.attr('commandValue')
          var img

          // commandValue 有可能是图片url，也有可能是表情的 value，需要区别对待

          if (emotionsShow === 'icon') {
            // 插入图片
            editor.command(e, 'InsertImage', commandValue)
          } else {
            // 插入value
            editor.command(e, 'insertHtml', '<span>' + commandValue + '</span>')
          }

          e.preventDefault()
        })

        // 添加panel
        menu.dropPanel = new E.DropPanel(editor, menu, {
          $content: $panelContent,
          width: 350
        })

        // 定义click事件（异步加载表情图片）
        menu.clickEvent = function(e) {
          var menu = this
          var dropPanel = menu.dropPanel

          // -------------隐藏-------------
          if (dropPanel.isShowing) {
            dropPanel.hide()
            return
          }

          // -------------显示-------------
          dropPanel.show()

          // 异步加载图片
          if (menu.imgLoaded) {
            return
          }
          $contentContainer.find('img').each(function() {
            var $img = $(this)
            var _src = $img.attr('_src')
            $img.on('error', function() {
              E.error('加载不出表情图片 ' + _src)
            })
            $img.attr('src', _src)
            $img.removeAttr('_src')
          })
          menu.imgLoaded = true
        }

        // 增加到editor对象中
        editor.menus[menuId] = menu
      })
    })
    // img 菜单
    _e(function(E, $) {
      E.createMenu(function(check) {
        var menuId = 'img'
        if (!check(menuId)) {
          return
        }
        var editor = this
        var lang = editor.config.lang

        // 创建 menu 对象
        var menu = new E.Menu({
          editor: editor,
          id: menuId,
          title: lang.img
        })

        // 创建 panel content
        var $panelContent = $('<div class="panel-tab"></div>')
        var $tabContainer = $('<div class="tab-container"></div>')
        var $contentContainer = $('<div class="content-container"></div>')
        $panelContent.append($tabContainer).append($contentContainer)

        // tab
        var $uploadTab = $('<a href="#">' + lang.uploadImg + '</a>')
        var $linkTab = $('<a href="#">' + lang.linkImg + '</a>')
        $tabContainer.append($uploadTab).append($linkTab)

        // 上传图片 content
        var $uploadContent = $('<div class="content"></div>')
        $contentContainer.append($uploadContent)

        // 网络图片 content
        var $linkContent = $('<div class="content"></div>')
        $contentContainer.append($linkContent)
        linkContentHandler(editor, menu, $linkContent)

        // 添加panel
        menu.dropPanel = new E.DropPanel(editor, menu, {
          $content: $panelContent,
          width: 400,
          onRender: function() {
            // 渲染后的回调事件，用于执行自定义上传的init
            // 因为渲染之后，上传面板的dom才会被渲染到页面，才能让第三方空间获取到
            var init = editor.config.customUploadInit
            init && init.call(editor)
          }
        })

        // 增加到editor对象中
        editor.menus[menuId] = menu

        // tab 切换事件
        function tabToggle() {
          $uploadTab.click(function(e) {
            $tabContainer.children().removeClass('selected')
            $contentContainer.children().removeClass('selected')
            $uploadContent.addClass('selected')
            $uploadTab.addClass('selected')
            e.preventDefault()
          })
          $linkTab.click(function(e) {
            $tabContainer.children().removeClass('selected')
            $contentContainer.children().removeClass('selected')
            $linkContent.addClass('selected')
            $linkTab.addClass('selected')
            e.preventDefault()

            // focus input
            if (E.placeholder) {
              $linkContent.find('input[type=text]').focus()
            }
          })

          // 默认情况
          // $uploadTab.addClass('selected');
          // $uploadContent.addClass('selected');
          $uploadTab.click()
        }

        // 隐藏上传图片
        function hideUploadImg() {
          $tabContainer.remove()
          $uploadContent.remove()
          $linkContent.addClass('selected')
        }

        // 隐藏网络图片
        function hideLinkImg() {
          $tabContainer.remove()
          $linkContent.remove()
          $uploadContent.addClass('selected')
        }

        // 判断用户是否配置了上传图片
        editor.ready(function() {
          var editor = this
          var config = editor.config
          var uploadImgUrl = config.uploadImgUrl
          var customUpload = config.customUpload
          var linkImg = config.hideLinkImg
          var $uploadImgPanel

          if (uploadImgUrl || customUpload) {
            // 第一，暴露出 $uploadContent 以便用户自定义 ！！！重要
            editor.$uploadContent = $uploadContent

            // 第二，绑定tab切换事件
            tabToggle()

            if (linkImg) {
              // 隐藏网络图片
              hideLinkImg()
            }
          } else {
            // 未配置上传图片功能
            hideUploadImg()
          }

          // 点击 $uploadContent 立即隐藏 dropPanel
          // 为了兼容IE8、9的上传，因为IE8、9使用 modal 上传
          // 这里使用异步，为了不妨碍高级浏览器通过点击 $uploadContent 选择文件
          function hidePanel() {
            menu.dropPanel.hide()
          }
          $uploadContent.click(function() {
            setTimeout(hidePanel)
          })
        })
      })

      // --------------- 处理网络图片content ---------------
      function linkContentHandler(editor, menu, $linkContent) {
        var lang = editor.config.lang
        var $urlContainer = $('<div style="margin:20px 10px 10px 10px;"></div>')
        var $urlInput = $(
          '<input type="text" class="block" placeholder="http://"/>'
        )
        $urlContainer.append($urlInput)
        var $btnSubmit = $('<button class="right">' + lang.submit + '</button>')
        var $btnCancel = $(
          '<button class="right gray">' + lang.cancel + '</button>'
        )

        $linkContent
          .append($urlContainer)
          .append($btnSubmit)
          .append($btnCancel)

        // 取消
        $btnCancel.click(function(e) {
          e.preventDefault()
          menu.dropPanel.hide()
        })

        // callback
        function callback() {
          $urlInput.val('')
        }

        // 确定
        $btnSubmit.click(function(e) {
          e.preventDefault()
          var url = $.trim($urlInput.val())
          if (!url) {
            // 无内容
            $urlInput.focus()
            return
          }

          var imgHtml = '<img style="max-width:100%;" src="' + url + '"/>'
          editor.command(e, 'insertHtml', imgHtml, callback)
        })
      }
    })
    // video 菜单
    _e(function(E, $) {
      E.createMenu(function(check) {
        var menuId = 'video'
        if (!check(menuId)) {
          return
        }
        var editor = this
        var lang = editor.config.lang
        var reg = /^<(iframe)|(embed)/i // <iframe... 或者 <embed... 格式

        // 创建 menu 对象
        var menu = new E.Menu({
          editor: editor,
          id: menuId,
          title: lang.video
        })

        // 创建 panel 内容
        var $content = $('<div></div>')
        var $linkInputContainer = $('<div style="margin:20px 10px;"></div>')
        var $linkInput = $(
          '<input type="text" class="block" placeholder=\'格式如：<iframe src="..." frameborder=0 allowfullscreen></iframe>\'/>'
        )
        $linkInputContainer.append($linkInput)
        var $sizeContainer = $('<div style="margin:20px 10px;"></div>')
        var $widthInput = $(
          '<input type="text" value="640" style="width:50px;text-align:center;"/>'
        )
        var $heightInput = $(
          '<input type="text" value="498" style="width:50px;text-align:center;"/>'
        )
        $sizeContainer
          .append('<span> ' + lang.width + ' </span>')
          .append($widthInput)
          .append('<span> px &nbsp;&nbsp;&nbsp;</span>')
          .append('<span> ' + lang.height + ' </span>')
          .append($heightInput)
          .append('<span> px </span>')
        var $btnContainer = $('<div></div>')
        var $howToCopy = $(
          '<a href="http://www.kancloud.cn/wangfupeng/wangeditor2/134973" target="_blank" style="display:inline-block;margin-top:10px;margin-left:10px;color:#999;">如何复制视频链接？</a>'
        )
        var $btnSubmit = $('<button class="right">' + lang.submit + '</button>')
        var $btnCancel = $(
          '<button class="right gray">' + lang.cancel + '</button>'
        )
        $btnContainer
          .append($howToCopy)
          .append($btnSubmit)
          .append($btnCancel)
        $content
          .append($linkInputContainer)
          .append($sizeContainer)
          .append($btnContainer)

        // 取消按钮
        $btnCancel.click(function(e) {
          e.preventDefault()
          $linkInput.val('')
          menu.dropPanel.hide()
        })

        // 确定按钮
        $btnSubmit.click(function(e) {
          e.preventDefault()
          var link = $.trim($linkInput.val())
          var $link
          var width = parseInt($widthInput.val())
          var height = parseInt($heightInput.val())
          var $div = $('<div>')
          var html = '<p>{content}</p>'

          // 验证数据
          if (!link) {
            menu.dropPanel.focusFirstInput()
            return
          }

          if (!reg.test(link)) {
            alert('视频链接格式错误！')
            menu.dropPanel.focusFirstInput()
            return
          }

          if (isNaN(width) || isNaN(height)) {
            alert('宽度或高度不是数字！')
            return
          }

          $link = $(link)

          // 设置高度和宽度
          $link.attr('width', width).attr('height', height)

          // 拼接字符串
          html = html.replace('{content}', $div.append($link).html())

          // 执行命令
          editor.command(e, 'insertHtml', html)
          $linkInput.val('')
        })

        // 创建panel
        menu.dropPanel = new E.DropPanel(editor, menu, {
          $content: $content,
          width: 400
        })

        // 增加到editor对象中
        editor.menus[menuId] = menu
      })
    })
    // insertcode 菜单
    _e(function(E, $) {
      // 加载 highlightjs 代码
      function loadHljs() {
        if (E.userAgent.indexOf('MSIE 8') > 0) {
          // 不支持 IE8
          return
        }
        if (window.hljs) {
          // 不要重复加载
          return
        }
        var script = document.createElement('script')
        script.type = 'text/javascript'
        script.src = '//cdn.bootcss.com/highlight.js/9.2.0/highlight.min.js'
        document.body.appendChild(script)
      }

      E.createMenu(function(check) {
        var menuId = 'insertcode'
        if (!check(menuId)) {
          return
        }

        // 加载 highlightjs 代码
        setTimeout(loadHljs, 0)

        var editor = this
        var config = editor.config
        var lang = config.lang
        var $txt = editor.txt.$txt

        // 创建 menu 对象
        var menu = new E.Menu({
          editor: editor,
          id: menuId,
          title: lang.insertcode
        })

        // click 事件
        menu.clickEvent = function(e) {
          var menu = this
          var dropPanel = menu.dropPanel

          // 隐藏
          if (dropPanel.isShowing) {
            dropPanel.hide()
            return
          }

          // 显示
          $textarea.val('')
          dropPanel.show()

          // highlightjs 语言列表
          var hljs = window.hljs
          if (hljs && hljs.listLanguages) {
            if ($langSelect.children().length !== 0) {
              return
            }
            $langSelect.css({
              'margin-top': '9px',
              'margin-left': '5px'
            })
            $.each(hljs.listLanguages(), function(key, lang) {
              if (lang === 'xml') {
                lang = 'html'
              }
              if (lang === config.codeDefaultLang) {
                $langSelect.append(
                  '<option value="' +
                    lang +
                    '" selected="selected">' +
                    lang +
                    '</option>'
                )
              } else {
                $langSelect.append(
                  '<option value="' + lang + '">' + lang + '</option>'
                )
              }
            })
          } else {
            $langSelect.hide()
          }
        }

        // 选中状态下的 click 事件
        menu.clickEventSelected = function(e) {
          var menu = this
          var dropPanel = menu.dropPanel

          // 隐藏
          if (dropPanel.isShowing) {
            dropPanel.hide()
            return
          }

          // 显示
          dropPanel.show()

          var rangeElem = editor.getRangeElem()
          var targetElem = editor.getSelfOrParentByName(rangeElem, 'pre')
          var $targetElem
          var className
          if (targetElem) {
            // 确定找到 pre 之后，再找 code
            targetElem = editor.getSelfOrParentByName(rangeElem, 'code')
          }
          if (!targetElem) {
            return
          }
          $targetElem = $(targetElem)

          // 赋值内容
          $textarea.val($targetElem.text())
          if ($langSelect) {
            // 赋值语言
            className = $targetElem.attr('class')
            if (className) {
              $langSelect.val(className.split(' ')[0])
            }
          }
        }

        // 定义更新选中状态的事件
        menu.updateSelectedEvent = function() {
          var self = this //菜单对象
          var editor = self.editor
          var rangeElem

          rangeElem = editor.getRangeElem()
          rangeElem = editor.getSelfOrParentByName(rangeElem, 'pre')

          if (rangeElem) {
            return true
          }

          return false
        }

        // 创建 panel
        var $content = $('<div></div>')
        var $textarea = $('<textarea></textarea>')
        var $langSelect = $('<select></select>')
        contentHandle($content)
        menu.dropPanel = new E.DropPanel(editor, menu, {
          $content: $content,
          width: 500
        })

        // 增加到editor对象中
        editor.menus[menuId] = menu

        // ------ 增加 content 内容 ------
        function contentHandle($content) {
          // textarea 区域
          var $textareaContainer = $('<div></div>')
          $textareaContainer.css({
            margin: '15px 5px 5px 5px',
            height: '160px',
            'text-align': 'center'
          })
          $textarea.css({
            width: '100%',
            height: '100%',
            padding: '10px'
          })
          $textarea.on('keydown', function(e) {
            // 取消 tab 键默认行为
            if (e.keyCode === 9) {
              e.preventDefault()
            }
          })
          $textareaContainer.append($textarea)
          $content.append($textareaContainer)

          // 按钮区域
          var $btnContainer = $('<div></div>')
          var $btnSubmit = $(
            '<button class="right">' + lang.submit + '</button>'
          )
          var $btnCancel = $(
            '<button class="right gray">' + lang.cancel + '</button>'
          )

          $btnContainer
            .append($btnSubmit)
            .append($btnCancel)
            .append($langSelect)
          $content.append($btnContainer)

          // 取消按钮
          $btnCancel.click(function(e) {
            e.preventDefault()
            menu.dropPanel.hide()
          })

          // 确定按钮
          var codeTpl =
            '<pre style="max-width:100%;overflow-x:auto;"><code{#langClass}>{#content}</code></pre>'
          $btnSubmit.click(function(e) {
            e.preventDefault()
            var val = $textarea.val()
            if (!val) {
              // 无内容
              $textarea.focus()
              return
            }

            var rangeElem = editor.getRangeElem()
            if (
              $.trim($(rangeElem).text()) &&
              codeTpl.indexOf('<p><br></p>') !== 0
            ) {
              codeTpl = '<p><br></p>' + codeTpl
            }

            var lang = $langSelect ? $langSelect.val() : '' // 获取高亮语言
            var langClass = ''
            var doHightlight = function() {
              $txt.find('pre code').each(function(i, block) {
                var $block = $(block)
                if ($block.attr('codemark')) {
                  // 有 codemark 标记的代码块，就不再重新格式化了
                  return
                } else if (window.hljs) {
                  // 新代码块，格式化之后，立即标记 codemark
                  window.hljs.highlightBlock(block)
                  $block.attr('codemark', '1')
                }
              })
            }

            // 语言高亮样式
            if (lang) {
              langClass = ' class="' + lang + ' hljs"'
            }

            // 替换标签
            val = val
              .replace(/&/gm, '&amp;')
              .replace(/</gm, '&lt;')
              .replace(/>/gm, '&gt;')

            // ---- menu 未选中状态 ----
            if (!menu.selected) {
              // 拼接html
              var html = codeTpl
                .replace('{#langClass}', langClass)
                .replace('{#content}', val)
              editor.command(e, 'insertHtml', html, doHightlight)
              return
            }

            // ---- menu 选中状态 ----
            var targetElem = editor.getSelfOrParentByName(rangeElem, 'pre')
            var $targetElem
            if (targetElem) {
              // 确定找到 pre 之后，再找 code
              targetElem = editor.getSelfOrParentByName(rangeElem, 'code')
            }
            if (!targetElem) {
              return
            }
            $targetElem = $(targetElem)

            function commandFn() {
              var className
              if (lang) {
                className = $targetElem.attr('class')
                if (className !== lang + ' hljs') {
                  $targetElem.attr('class', lang + ' hljs')
                }
              }
              $targetElem.html(val)
            }
            function callback() {
              editor.restoreSelectionByElem(targetElem)
              doHightlight()
            }
            editor.customCommand(e, commandFn, callback)
          })
        }

        // ------ enter 时，不另起标签，只换行 ------
        $txt.on('keydown', function(e) {
          if (e.keyCode !== 13) {
            return
          }
          var rangeElem = editor.getRangeElem()
          var targetElem = editor.getSelfOrParentByName(rangeElem, 'code')
          if (!targetElem) {
            return
          }

          editor.command(e, 'insertHtml', '\n')
        })

        // ------ 点击时，禁用其他标签 ------
        function updateMenu() {
          var rangeElem = editor.getRangeElem()
          var targetElem = editor.getSelfOrParentByName(rangeElem, 'code')
          if (targetElem) {
            // 在 code 之内，禁用其他菜单
            editor.disableMenusExcept('insertcode')
          } else {
            // 不是在 code 之内，启用其他菜单
            editor.enableMenusExcept('insertcode')
          }
        }
        $txt.on('keydown click', function(e) {
          // 此处必须使用 setTimeout 异步处理，否则不对
          setTimeout(updateMenu)
        })
      })
    })
    // undo 菜单
    _e(function(E, $) {
      E.createMenu(function(check) {
        var menuId = 'undo'
        if (!check(menuId)) {
          return
        }
        var editor = this
        var lang = editor.config.lang

        // 创建 menu 对象
        var menu = new E.Menu({
          editor: editor,
          id: menuId,
          title: lang.undo
        })

        // click 事件
        menu.clickEvent = function(e) {
          editor.undo()
        }

        // 增加到editor对象中
        editor.menus[menuId] = menu

        // ------------ 初始化时、enter 时、打字中断时，做记录 ------------
        // ------------ ctrl + z 是调用记录撤销，而不是使用浏览器默认的撤销 ------------
        editor.ready(function() {
          var editor = this
          var $txt = editor.txt.$txt
          var timeoutId

          // 执行undo记录
          function undo() {
            editor.undoRecord()
          }

          $txt.on('keydown', function(e) {
            var keyCode = e.keyCode

            // 撤销 ctrl + z
            if (e.ctrlKey && keyCode === 90) {
              editor.undo()
              return
            }

            if (keyCode === 13) {
              // enter 做记录
              undo()
            } else {
              // keyup 之后 1s 之内不操作，则做一次记录
              if (timeoutId) {
                clearTimeout(timeoutId)
              }
              timeoutId = setTimeout(undo, 1000)
            }
          })

          // 初始化做记录
          editor.undoRecord()
        })
      })
    })
    // redo 菜单
    _e(function(E, $) {
      E.createMenu(function(check) {
        var menuId = 'redo'
        if (!check(menuId)) {
          return
        }
        var editor = this
        var lang = editor.config.lang

        // 创建 menu 对象
        var menu = new E.Menu({
          editor: editor,
          id: menuId,
          title: lang.redo
        })

        // click 事件
        menu.clickEvent = function(e) {
          editor.redo()
        }

        // 增加到editor对象中
        editor.menus[menuId] = menu
      })
    })
    // 全屏 菜单
    _e(function(E, $) {
      // 记录全屏时的scrollTop
      var scrollTopWhenFullScreen

      E.createMenu(function(check) {
        var menuId = 'fullscreen'
        if (!check(menuId)) {
          return
        }
        var editor = this
        var $txt = editor.txt.$txt
        var config = editor.config
        var zIndexConfig = config.zindex || 10000
        var lang = config.lang

        var isSelected = false
        var zIndex

        var maxHeight

        // 创建 menu 对象
        var menu = new E.Menu({
          editor: editor,
          id: menuId,
          title: lang.fullscreen
        })

        // 定义click事件
        menu.clickEvent = function(e) {
          // 增加样式
          var $editorContainer = editor.$editorContainer
          $editorContainer.addClass('wangEditor-fullscreen')

          // （先保存当前的）再设置z-index
          zIndex = $editorContainer.css('z-index')
          $editorContainer.css('z-index', zIndexConfig)

          var $wrapper
          var txtHeight = $txt.height()
          var txtOuterHeight = $txt.outerHeight()

          if (editor.useMaxHeight) {
            // 记录 max-height，并暂时去掉maxheight
            maxHeight = $txt.css('max-height')
            $txt.css('max-height', 'none')

            // 如果使用了maxHeight， 将$txt从它的父元素中移出来
            $wrapper = $txt.parent()
            $wrapper.after($txt)
            $wrapper.remove()
            $txt.css('overflow-y', 'auto')
          }

          // 设置高度到全屏
          var menuContainer = editor.menuContainer
          $txt.height(
            E.$window.height() -
              menuContainer.height() -
              (txtOuterHeight - txtHeight) // 去掉内边距和外边距
          )

          // 取消menuContainer的内联样式（menu吸顶时，会为 menuContainer 设置一些内联样式）
          editor.menuContainer.$menuContainer.attr('style', '')

          // 保存状态
          isSelected = true

          // 记录编辑器是否全屏
          editor.isFullScreen = true

          // 记录设置全屏时的高度
          scrollTopWhenFullScreen = E.$window.scrollTop()
        }

        // 定义选中状态的 click 事件
        menu.clickEventSelected = function(e) {
          // 取消样式
          var $editorContainer = editor.$editorContainer
          $editorContainer.removeClass('wangEditor-fullscreen')
          $editorContainer.css('z-index', zIndex)

          // 还原height
          if (editor.useMaxHeight) {
            $txt.css('max-height', maxHeight)
          } else {
            // editor.valueContainerHeight 在 editor.txt.initHeight() 中事先保存了
            editor.$valueContainer.css('height', editor.valueContainerHeight)
          }

          // 重新计算高度
          editor.txt.initHeight()

          // 保存状态
          isSelected = false

          // 记录编辑器是否全屏
          editor.isFullScreen = false

          // 还原scrollTop
          if (scrollTopWhenFullScreen != null) {
            E.$window.scrollTop(scrollTopWhenFullScreen)
          }
        }

        // 定义选中事件
        menu.updateSelectedEvent = function(e) {
          return isSelected
        }

        // 增加到editor对象中
        editor.menus[menuId] = menu
      })
    })
    // 渲染menus
    _e(function(E, $) {
      E.fn.renderMenus = function() {
        var editor = this
        var menus = editor.menus
        var menuIds = editor.config.menus
        var menuContainer = editor.menuContainer

        var menu
        var groupIdx = 0
        $.each(menuIds, function(k, v) {
          if (v === '|') {
            groupIdx++
            return
          }

          menu = menus[v]
          if (menu) {
            menu.render(groupIdx)
          }
        })
      }
    })
    // 渲染menus
    _e(function(E, $) {
      E.fn.renderMenuContainer = function() {
        var editor = this
        var menuContainer = editor.menuContainer
        var $editorContainer = editor.$editorContainer

        menuContainer.render()
      }
    })
    // 渲染 txt
    _e(function(E, $) {
      E.fn.renderTxt = function() {
        var editor = this
        var txt = editor.txt

        txt.render()

        // ready 时候，计算txt的高度
        editor.ready(function() {
          txt.initHeight()
        })
      }
    })
    // 渲染 container
    _e(function(E, $) {
      E.fn.renderEditorContainer = function() {
        var editor = this
        var $valueContainer = editor.$valueContainer
        var $editorContainer = editor.$editorContainer
        var $txt = editor.txt.$txt
        var $prev, $parent

        // 将编辑器渲染到页面中
        if ($valueContainer === $txt) {
          $prev = editor.$prev
          $parent = editor.$parent

          if ($prev && $prev.length) {
            // 有前置节点，就插入到前置节点的后面
            $prev.after($editorContainer)
          } else {
            // 没有前置节点，就直接插入到父元素
            $parent.prepend($editorContainer)
          }
        } else {
          $valueContainer.after($editorContainer)
          $valueContainer.hide()
        }

        // 设置宽度（这样设置宽度有问题）
        // $editorContainer.css('width', $valueContainer.css('width'));
      }
    })
    // 菜单事件
    _e(function(E, $) {
      // 绑定每个菜单的click事件
      E.fn.eventMenus = function() {
        var menus = this.menus

        // 绑定菜单的点击事件
        $.each(menus, function(k, v) {
          v.bindEvent()
        })
      }
    })
    // 菜单container事件
    _e(function(E, $) {
      E.fn.eventMenuContainer = function() {}
    })
    // 编辑区域事件
    _e(function(E, $) {
      E.fn.eventTxt = function() {
        var txt = this.txt

        // txt内容变化时，保存选区
        txt.saveSelectionEvent()

        // txt内容变化时，随时更新 value
        txt.updateValueEvent()

        // txt内容变化时，随时更新 menu style
        txt.updateMenuStyleEvent()

        // // 鼠标hover时，显示 p head 高度（暂时关闭这个功能）
        // if (!/ie/i.test(E.userAgent)) {
        //     // 暂时不支持IE
        //     txt.showHeightOnHover();
        // }
      }
    })
    // 上传图片事件
    _e(function(E, $) {
      E.plugin(function() {
        var editor = this
        var fns = editor.config.uploadImgFns // editor.config.uploadImgFns = {} 在config文件中定义了

        // -------- 定义load函数 --------
        fns.onload ||
          (fns.onload = function(resultText, xhr) {
            E.log('上传结束，返回结果为 ' + resultText)

            var editor = this
            var originalName = editor.uploadImgOriginalName || '' // 上传图片时，已经将图片的名字存在 editor.uploadImgOriginalName
            var img
            if (resultText.indexOf('error|') === 0) {
              // 提示错误
              E.warn('上传失败：' + resultText.split('|')[1])
              alert(resultText.split('|')[1])
            } else {
              E.log('上传成功，即将插入编辑区域，结果为：' + resultText)

              // 将结果插入编辑器
              img = document.createElement('img')
              img.onload = function() {
                var html =
                  '<img src="' +
                  resultText +
                  '" alt="' +
                  originalName +
                  '" style="max-width:100%;"/>'
                editor.command(null, 'insertHtml', html)

                E.log('已插入图片，地址 ' + resultText)
                img = null
              }
              img.onerror = function() {
                E.error(
                  '使用返回的结果获取图片，发生错误。请确认以下结果是否正确：' +
                    resultText
                )
                img = null
              }
              img.src = resultText
            }
          })

        // -------- 定义tiemout函数 --------
        fns.ontimeout ||
          (fns.ontimeout = function(xhr) {
            E.error('上传图片超时')
            alert('上传图片超时')
          })

        // -------- 定义error函数 --------
        fns.onerror ||
          (fns.onerror = function(xhr) {
            E.error('上传上图片发生错误')
            alert('上传上图片发生错误')
          })
      })
    })
    // xhr 上传图片
    _e(function(E, $) {
      if (!window.FileReader || !window.FormData) {
        // 如果不支持html5的文档操作，直接返回
        return
      }

      E.plugin(function() {
        var editor = this
        var config = editor.config
        var uploadImgUrl = config.uploadImgUrl
        var uploadTimeout = config.uploadTimeout

        // 获取配置中的上传事件
        var uploadImgFns = config.uploadImgFns
        var onload = uploadImgFns.onload
        var ontimeout = uploadImgFns.ontimeout
        var onerror = uploadImgFns.onerror

        if (!uploadImgUrl) {
          return
        }

        // -------- 将以base64的图片url数据转换为Blob --------
        function convertBase64UrlToBlob(urlData, filetype) {
          //去掉url的头，并转换为byte
          var bytes = window.atob(urlData.split(',')[1])

          //处理异常,将ascii码小于0的转换为大于0
          var ab = new ArrayBuffer(bytes.length)
          var ia = new Uint8Array(ab)
          var i
          for (i = 0; i < bytes.length; i++) {
            ia[i] = bytes.charCodeAt(i)
          }

          return new Blob([ab], { type: filetype })
        }

        // -------- 插入图片的方法 --------
        function insertImg(src, event) {
          var img = document.createElement('img')
          img.onload = function() {
            var html = '<img src="' + src + '" style="max-width:100%;"/>'
            editor.command(event, 'insertHtml', html)

            E.log('已插入图片，地址 ' + src)
            img = null
          }
          img.onerror = function() {
            E.error(
              '使用返回的结果获取图片，发生错误。请确认以下结果是否正确：' + src
            )
            img = null
          }
          img.src = src
        }

        // -------- onprogress 事件 --------
        function updateProgress(e) {
          if (e.lengthComputable) {
            var percentComplete = e.loaded / e.total
            editor.showUploadProgress(percentComplete * 100)
          }
        }

        // -------- xhr 上传图片 --------
        editor.xhrUploadImg = function(opt) {
          // opt 数据
          var event = opt.event
          var base64 = opt.base64
          var fileType = opt.fileType || 'image/png' // 无扩展名则默认使用 png
          var fileName = opt.filename || ('temp_'+parseInt(Math.random()*10000)+'.'+fileType.replace('image/',''))
          var name = opt.name || 'wangEditor_upload_file'
          var loadfn = opt.loadfn || onload
          var errorfn = opt.errorfn || onerror
          var timeoutfn = opt.timeoutfn || ontimeout

          // 上传参数（如 token）
          var params = editor.config.uploadParams || {}

          // headers
          var headers = editor.config.uploadHeaders || {}

          // 获取文件扩展名
          var fileExt = 'png' // 默认为 png
          if (fileName.indexOf('.') > 0) {
            // 原来的文件名有扩展名
            fileExt = fileName.slice(
              fileName.lastIndexOf('.') - fileName.length + 1
            )
          } else if (fileType.indexOf('/') > 0 && fileType.split('/')[1]) {
            // 文件名没有扩展名，通过类型获取，如从 'image/png' 取 'png'
            fileExt = fileType.split('/')[1]
          }

          // ------------ begin 预览模拟上传 ------------
          if (E.isOnWebsite) {
            E.log('预览模拟上传')
            insertImg(base64, event)
            return
          }
          // ------------ end 预览模拟上传 ------------

          // 变量声明
          var xhr = new XMLHttpRequest()
          var timeoutId
          var src
          var formData = new FormData()

          // 超时处理
          function timeoutCallback() {
            if (timeoutId) {
              clearTimeout(timeoutId)
            }
            if (xhr && xhr.abort) {
              xhr.abort()
            }

            // 超时了就阻止默认行为
            event.preventDefault()

            // 执行回调函数，提示什么内容，都应该在回调函数中定义
            timeoutfn && timeoutfn.call(editor, xhr, fileName)

            // 隐藏进度条
            editor.hideUploadProgress()
          }

          xhr.onload = function() {
            if (timeoutId) {
              clearTimeout(timeoutId)
            }

            // 记录文件名到 editor.uploadImgOriginalName ，插入图片时，可做 alt 属性用
            editor.uploadImgOriginalName = fileName
            if (fileName.indexOf('.') > 0) {
              editor.uploadImgOriginalName = fileName.split('.')[0]
            }
            console.log(fileName)
            // 执行load函数，任何操作，都应该在load函数中定义
            loadfn && loadfn.call(editor, xhr.responseText, xhr, fileName)

            // 隐藏进度条
            editor.hideUploadProgress()
          }
          xhr.onerror = function() {
            if (timeoutId) {
              clearTimeout(timeoutId)
            }

            // 超时了就阻止默认行为
            event.preventDefault()

            // 执行error函数，错误提示，应该在error函数中定义
            errorfn && errorfn.call(editor, xhr, fileName)

            // 隐藏进度条
            editor.hideUploadProgress()
          }
          // xhr.onprogress = updateProgress;
          xhr.upload.onprogress = updateProgress

          // 填充数据
          formData.append(
            name,
            convertBase64UrlToBlob(base64, fileType),
            fileName // E.random() + '.' + fileExt
          )

          // 添加参数
          $.each(params, function(key, value) {
            formData.append(key, value)
          })

          // 开始上传
          xhr.open('POST', uploadImgUrl, true)
          // xhr.setRequestHeader("Content-type","application/x-www-form-urlencoded");  // 将参数解析成传统form的方式上传

          // 修改自定义配置的headers
          $.each(headers, function(key, value) {
            xhr.setRequestHeader(key, value)
          })

          // 跨域上传时，传cookie
          xhr.withCredentials = !!editor.config.withCredentials

          // 发送数据
          xhr.send(formData)
          timeoutId = setTimeout(timeoutCallback, uploadTimeout)

          E.log('开始上传...并开始超时计算')
        }
      })
    })
    // 进度条
    _e(function(E, $) {
      E.plugin(function() {
        var editor = this
        var menuContainer = editor.menuContainer
        var menuHeight = menuContainer.height()
        var $editorContainer = editor.$editorContainer
        var width = $editorContainer.width()
        var $progress = $('<div class="wangEditor-upload-progress"></div>')

        // 渲染事件
        var isRender = false
        function render() {
          if (isRender) {
            return
          }
          isRender = true

          $progress.css({
            top: menuHeight + 'px'
          })
          $editorContainer.append($progress)
        }

        // ------ 显示进度 ------
        editor.showUploadProgress = function(progress) {
          if (timeoutId) {
            clearTimeout(timeoutId)
          }

          // 显示之前，先判断是否渲染
          render()

          $progress.show()
          $progress.width((progress * width) / 100)
        }

        // ------ 隐藏进度条 ------
        var timeoutId
        function hideProgress() {
          $progress.hide()
          timeoutId = null
        }
        editor.hideUploadProgress = function(time) {
          if (timeoutId) {
            clearTimeout(timeoutId)
          }
          time = time || 750
          timeoutId = setTimeout(hideProgress, time)
        }
      })
    })
    // upload img 插件
    _e(function(E, $) {
      E.plugin(function() {
        var editor = this
        var config = editor.config
        var uploadImgUrl = config.uploadImgUrl
        var uploadTimeout = config.uploadTimeout
        var event

        if (!uploadImgUrl) {
          return
        }

        // 获取editor的上传dom
        var $uploadContent = editor.$uploadContent
        if (!$uploadContent) {
          return
        }

        // 自定义UI，并添加到上传dom节点上
        var $uploadIcon = $(
          '<div class="upload-icon-container"><i class="wangeditor-menu-img-upload"></i></div>'
        )
        $uploadContent.append($uploadIcon)

        // ---------- 构建上传对象 ----------
        var upfile = new E.UploadFile({
          editor: editor,
          uploadUrl: uploadImgUrl,
          timeout: uploadTimeout,
          fileAccept: 'image/jpg,image/jpeg,image/png,image/gif,image/bmp' // 只允许选择图片
        })

        // 选择本地文件，上传
        $uploadIcon.click(function(e) {
          event = e
          upfile.selectFiles()
        })
      })
    })
    // h5 方式上传图片
    _e(function(E, $) {
      if (!window.FileReader || !window.FormData) {
        // 如果不支持html5的文档操作，直接返回
        return
      }

      // 构造函数
      var UploadFile = function(opt) {
        this.editor = opt.editor
        this.uploadUrl = opt.uploadUrl
        this.timeout = opt.timeout
        this.fileAccept = opt.fileAccept
        this.multiple = true
      }

      UploadFile.fn = UploadFile.prototype

      // clear
      UploadFile.fn.clear = function() {
        this.$input.val('')
        E.log('input value 已清空')
      }

      // 渲染
      UploadFile.fn.render = function() {
        var self = this
        if (self._hasRender) {
          // 不要重复渲染
          return
        }

        E.log('渲染dom')

        var fileAccept = self.fileAccept
        var acceptTpl = fileAccept ? 'accept="' + fileAccept + '"' : ''
        var multiple = self.multiple
        var multipleTpl = multiple ? 'multiple="multiple"' : ''
        var $input = $(
          '<input type="file" ' + acceptTpl + ' ' + multipleTpl + '/>'
        )
        var $container = $('<div style="display:none;"></div>')

        $container.append($input)
        E.$body.append($container)

        // onchange 事件
        $input.on('change', function(e) {
          self.selected(e, $input.get(0))
        })

        // 记录对象数据
        self.$input = $input

        // 记录
        self._hasRender = true
      }

      // 选择
      UploadFile.fn.selectFiles = function() {
        var self = this

        E.log('使用 html5 方式上传')

        // 先渲染
        self.render()

        // 选择
        E.log('选择文件')
        self.$input.click()
      }

      // 选中文件之后
      UploadFile.fn.selected = function(e, input) {
        var self = this
        var files = input.files || []
        if (files.length === 0) {
          return
        }

        E.log('选中 ' + files.length + ' 个文件')

        // 遍历选中的文件，预览、上传
        $.each(files, function(key, value) {
          self.upload(value)
        })
      }

      // 上传单个文件
      UploadFile.fn.upload = function(file) {
        var self = this
        var editor = self.editor
        var filename = file.name || ''
        var fileType = file.type || ''
        var uploadImgFns = editor.config.uploadImgFns
        var lang = editor.config.lang
        var uploadFileName =
          editor.config.uploadImgFileName || 'wangEditorH5File'
        var onload = uploadImgFns.onload
        var ontimeout = uploadImgFns.ontimeout
        var onerror = uploadImgFns.onerror
        var reader = new FileReader()

        if (!onload || !ontimeout || !onerror) {
          E.error('请为编辑器配置上传图片的 onload ontimeout onerror 回调事件')
          return
        }

        E.log('开始执行 ' + filename + ' 文件的上传')

        // 清空 input 数据
        function clearInput() {
          self.clear()
        }
        editor.command(
          null,
          'insertHtml',
          '<img alt="' +
            lang.uploadPlaceTxt +
            filename +
            '" src="' +
            editor.config.uploadPlaceholderImg +
            '"/>'
        )
        // onload事件
        reader.onload = function(e) {
          E.log('已读取' + filename + '文件')

          var base64 = e.target.result || this.result
          editor.xhrUploadImg({
            event: e,
            filename: filename,
            base64: base64,
            fileType: fileType,
            name: uploadFileName,
            loadfn: function(resultText, xhr, fileName) {
              clearInput()
              // 执行配置中的方法
              var editor = this
              onload.call(editor, resultText, xhr, fileName)
            },
            errorfn: function(xhr, fileName) {
              clearInput()
              if (E.isOnWebsite) {
                alert(
                  'wangEditor官网暂时没有服务端，因此报错。实际项目中不会发生'
                )
              }
              // 执行配置中的方法
              var editor = this
              onerror.call(editor, xhr, fileName)
            },
            timeoutfn: function(xhr, fileName) {
              clearInput()
              if (E.isOnWebsite) {
                alert(
                  'wangEditor官网暂时没有服务端，因此超时。实际项目中不会发生'
                )
              }
              // 执行配置中的方法
              var editor = this
              ontimeout(editor, xhr, fileName)
            }
          })
        }

        // 开始取文件
        reader.readAsDataURL(file)
      }

      // 暴露给 E
      E.UploadFile = UploadFile
    })
    // form方式上传图片
    _e(function(E, $) {
      if (window.FileReader && window.FormData) {
        // 如果支持 html5 上传，则返回
        return
      }

      // 构造函数
      var UploadFile = function(opt) {
        this.editor = opt.editor
        this.uploadUrl = opt.uploadUrl
        this.timeout = opt.timeout
        this.fileAccept = opt.fileAccept
        this.multiple = false
      }

      UploadFile.fn = UploadFile.prototype

      // clear
      UploadFile.fn.clear = function() {
        this.$input.val('')
        E.log('input value 已清空')
      }

      // 隐藏modal
      UploadFile.fn.hideModal = function() {
        this.modal.hide()
      }

      // 渲染
      UploadFile.fn.render = function() {
        var self = this
        var editor = self.editor
        var uploadFileName =
          editor.config.uploadImgFileName || 'wangEditorFormFile'
        if (self._hasRender) {
          // 不要重复渲染
          return
        }

        // 服务器端路径
        var uploadUrl = self.uploadUrl

        E.log('渲染dom')

        // 创建 form 和 iframe
        var iframeId = 'iframe' + E.random()
        var $iframe = $(
          '<iframe name="' +
            iframeId +
            '" id="' +
            iframeId +
            '" frameborder="0" width="0" height="0"></iframe>'
        )
        var multiple = self.multiple
        var multipleTpl = multiple ? 'multiple="multiple"' : ''
        var $p = $('<p>选择图片并上传</p>')
        var $input = $(
          '<input type="file" ' +
            multipleTpl +
            ' name="' +
            uploadFileName +
            '"/>'
        )
        var $btn = $('<input type="submit" value="上传"/>')
        var $form = $(
          '<form enctype="multipart/form-data" method="post" action="' +
            uploadUrl +
            '" target="' +
            iframeId +
            '"></form>'
        )
        var $container = $('<div style="margin:10px 20px;"></div>')

        $form
          .append($p)
          .append($input)
          .append($btn)

        // 增加用户配置的参数，如 token
        $.each(editor.config.uploadParams, function(key, value) {
          $form.append(
            $('<input type="hidden" name="' + key + '" value="' + value + '"/>')
          )
        })

        $container.append($form)
        $container.append($iframe)

        self.$input = $input
        self.$iframe = $iframe

        // 生成 modal
        var modal = new E.Modal(editor, undefined, {
          $content: $container
        })
        self.modal = modal

        // 记录
        self._hasRender = true
      }

      // 绑定 iframe load 事件
      UploadFile.fn.bindLoadEvent = function() {
        var self = this
        if (self._hasBindLoad) {
          // 不要重复绑定
          return
        }

        var editor = self.editor
        var $iframe = self.$iframe
        var iframe = $iframe.get(0)
        var iframeWindow = iframe.contentWindow
        var onload = editor.config.uploadImgFns.onload

        // 定义load事件
        function onloadFn() {
          var resultText = $.trim(iframeWindow.document.body.innerHTML)
          if (!resultText) {
            return
          }

          // 获取文件名
          var fileFullName = self.$input.val() // 结果如 C:\folder\abc.png 格式
          var fileOriginalName = fileFullName
          if (fileFullName.lastIndexOf('\\') >= 0) {
            // 获取 abc.png 格式
            fileOriginalName = fileFullName.slice(
              fileFullName.lastIndexOf('\\') + 1
            )
            if (fileOriginalName.indexOf('.') > 0) {
              // 获取 abc （即不带扩展名的文件名）
              fileOriginalName = fileOriginalName.split('.')[0]
            }
          }

          // 将文件名暂存到 editor.uploadImgOriginalName ，插入图片时，可作为 alt 属性来用
          editor.uploadImgOriginalName = fileOriginalName

          // 执行load函数，插入图片的操作，应该在load函数中执行
          onload.call(editor, resultText)

          // 清空 input 数据
          self.clear()

          // 隐藏modal
          self.hideModal()
        }

        // 绑定 load 事件
        if (iframe.attachEvent) {
          iframe.attachEvent('onload', onloadFn)
        } else {
          iframe.onload = onloadFn
        }

        // 记录
        self._hasBindLoad = true
      }

      UploadFile.fn.show = function() {
        var self = this
        var modal = self.modal

        function show() {
          modal.show()
          self.bindLoadEvent()
        }
        setTimeout(show)
      }

      // 选择
      UploadFile.fn.selectFiles = function() {
        var self = this

        E.log('使用 form 方式上传')

        // 先渲染
        self.render()

        // 先清空
        self.clear()

        // 显示
        self.show()
      }

      // 暴露给 E
      E.UploadFile = UploadFile
    })
    // upload img 插件 粘贴图片
    _e(function(E, $) {
      E.plugin(function() {
        var editor = this
        var txt = editor.txt
        var $txt = txt.$txt
        var config = editor.config
        var uploadImgUrl = config.uploadImgUrl
        var uploadFileName = config.uploadImgFileName || 'wangEditorPasteFile'
        var pasteEvent
        var $imgsBeforePaste

        // 未配置上传图片url，则忽略
        if (!uploadImgUrl) {
          return
        }

        // -------- 非 chrome 下，通过查找粘贴的图片的方式上传 --------
        function findPasteImgAndUpload() {
          var reg = /^data:(image\/\w+);base64/
          var $imgs = $txt.find('img')

          E.log(
            '粘贴后，检查到编辑器有' +
              $imgs.length +
              '个图片。开始遍历图片，试图找到刚刚粘贴过来的图片'
          )

          $.each($imgs, function() {
            var img = this
            var $img = $(img)
            var flag
            var base64 = $img.attr('src')
            var type

            // 判断当前图片是否是粘贴之前的
            $imgsBeforePaste.each(function() {
              if (img === this) {
                // 当前图片是粘贴之前的
                flag = true
                return false
              }
            })

            // 当前图片是粘贴之前的，则忽略
            if (flag) {
              return
            }

            E.log('找到一个粘贴过来的图片')

            if (reg.test(base64)) {
              // 得到的粘贴的图片是 base64 格式，符合要求
              E.log('src 是 base64 格式，可以上传')
              type = base64.match(reg)[1]
              editor.xhrUploadImg({
                event: pasteEvent,
                base64: base64,
                fileType: type,
                name: uploadFileName
              })
            } else {
              E.log('src 为 ' + base64 + ' ，不是 base64 格式，暂时不支持上传')
            }

            // 最终移除原图片
            $img.remove()
          })

          E.log('遍历结束')
        }

        // 开始监控粘贴事件
        $txt.on('paste', function(e) {
          pasteEvent = e
          var data =
            pasteEvent.clipboardData || pasteEvent.originalEvent.clipboardData
          var text
          var items

          // -------- 试图获取剪切板中的文字，有文字的情况下，就不处理图片粘贴 --------
          if (data == null) {
            text = window.clipboardData && window.clipboardData.getData('text')
          } else {
            text = data.getData('text/plain') || data.getData('text/html')
          }
          if (text) {
            return
          }

          items = data && data.items
          if (items) {
            // -------- chrome 可以用 data.items 取出图片 -----
            E.log('通过 data.items 得到了数据')
            console.log(items)
            $.each(items, function(key, value) {
              var fileType = value.type || ''
              if (fileType.indexOf('image') < 0) {
                // 不是图片
                return
              }

              var file = value.getAsFile()
              var reader = new FileReader()

              E.log('得到一个粘贴图片')
              reader.onload = function(e) {
                E.log('读取到粘贴的图片')

                // 执行上传
                var base64 = e.target.result || this.result
                editor.xhrUploadImg({
                  event: pasteEvent,
                  base64: base64,
                  fileType: fileType,
                  name: uploadFileName
                })
              }

              //读取粘贴的文件
              reader.readAsDataURL(file)
            })
          } else {
            // -------- 非 chrome 不能用 data.items 取图片 -----

            E.log('未从 data.items 得到数据，使用检测粘贴图片的方式')

            // 获取
            $imgsBeforePaste = $txt.find('img')
            E.log('粘贴前，检查到编辑器有' + $imgsBeforePaste.length + '个图片')

            // 异步上传找到的图片
            setTimeout(findPasteImgAndUpload, 0)
          }
        })
      })
    })
    // 拖拽上传图片 插件
    _e(function(E, $) {
      E.plugin(function() {
        var editor = this
        var txt = editor.txt
        var $txt = txt.$txt
        var config = editor.config
        var lang = editor.config.lang
        var uploadImgUrl = config.uploadImgUrl
        var uploadFileName = config.uploadImgFileName || 'wangEditorDragFile'

        // 未配置上传图片url，则忽略
        if (!uploadImgUrl) {
          return
        }

        // 阻止浏览器默认行为
        E.$document.on('dragleave drop dragenter dragover', function(e) {
          e.preventDefault()
        })

        // 监控 $txt drop 事件
        $txt.on('drop', function(dragEvent) {
          dragEvent.preventDefault()

          var originalEvent = dragEvent.originalEvent
          var files =
            originalEvent.dataTransfer && originalEvent.dataTransfer.files

          if (!files || !files.length) {
            return
          }

          $.each(files, function(k, file) {
            var type = file.type
            var name = file.name

            if (type.indexOf('image/') < 0) {
              // 只接收图片
              return
            }

            E.log('得到图片 ' + name)

            editor.command(
              null,
              'insertHtml',
              '<img alt="' +
                lang.uploadPlaceTxt +
                name +
                '"  src="' +
                editor.config.uploadPlaceholderImg +
                '" />'
            )
            var reader = new FileReader()
            reader.onload = function(e) {
              E.log('读取到图片 ' + name)

              // 执行上传
              var base64 = e.target.result || this.result
              editor.xhrUploadImg({
                event: dragEvent,
                filename: name,
                base64: base64,
                fileType: type,
                name: uploadFileName
              })
            }

            //读取粘贴的文件
            reader.readAsDataURL(file)
          })
        })
      })
    })
    // 编辑器区域 table toolbar
    _e(function(E, $) {
      E.plugin(function() {
        var editor = this
        var txt = editor.txt
        var $txt = txt.$txt
        var html = ''
        // 说明：设置了 max-height 之后，$txt.parent() 负责滚动处理
        var $currentTxt = editor.useMaxHeight ? $txt.parent() : $txt
        var $currentTable

        // 用到的dom节点
        var isRendered = false
        var $toolbar = $('<div class="txt-toolbar"></div>')
        var $triangle = $('<div class="tip-triangle"></div>')
        var $delete = $(
          '<a href="#"><i class="wangeditor-menu-img-trash-o"></i></a>'
        )
        var $zoomSmall = $(
          '<a href="#"><i class="wangeditor-menu-img-search-minus"></i></a>'
        )
        var $zoomBig = $(
          '<a href="#"><i class="wangeditor-menu-img-search-plus"></i></a>'
        )

        // 渲染到页面
        function render() {
          if (isRendered) {
            return
          }

          // 绑定事件
          bindEvent()

          // 拼接 渲染到页面上
          $toolbar
            .append($triangle)
            .append($delete)
            .append($zoomSmall)
            .append($zoomBig)
          editor.$editorContainer.append($toolbar)
          isRendered = true
        }

        // 绑定事件
        function bindEvent() {
          // 统一执行命令的方法
          var commandFn
          function command(e, callback) {
            // 执行命令之前，先存储html内容
            html = $txt.html()
            // 监控内容变化
            var cb = function() {
              if (callback) {
                callback()
              }
              if (html !== $txt.html()) {
                $txt.change()
              }
            }
            // 执行命令
            if (commandFn) {
              editor.customCommand(e, commandFn, cb)
            }
          }

          // 删除
          $delete.click(function(e) {
            commandFn = function() {
              $currentTable.remove()
            }
            command(e, function() {
              setTimeout(hide, 100)
            })
          })

          // 放大
          $zoomBig.click(function(e) {
            commandFn = function() {
              $currentTable.css({
                width: '100%'
              })
            }
            command(e, function() {
              setTimeout(show)
            })
          })

          // 缩小
          $zoomSmall.click(function(e) {
            commandFn = function() {
              $currentTable.css({
                width: 'auto'
              })
            }
            command(e, function() {
              setTimeout(show)
            })
          })
        }

        // 显示 toolbar
        function show() {
          if (editor._disabled) {
            // 编辑器已经被禁用，则不让显示
            return
          }
          if ($currentTable == null) {
            return
          }
          $currentTable.addClass('clicked')
          var tablePosition = $currentTable.position()
          var tableTop = tablePosition.top
          var tableLeft = tablePosition.left
          var tableHeight = $currentTable.outerHeight()
          var tableWidth = $currentTable.outerWidth()

          // --- 定位 toolbar ---

          // 计算初步结果
          var top = tableTop + tableHeight
          var left = tableLeft
          var marginLeft = 0

          var txtTop = $currentTxt.position().top
          var txtHeight = $currentTxt.outerHeight()
          if (top > txtTop + txtHeight) {
            // top 不得超出编辑范围
            top = txtTop + txtHeight
          }

          // 显示（方便计算 margin）
          $toolbar.show()

          // 计算 margin
          var width = $toolbar.outerWidth()
          marginLeft = tableWidth / 2 - width / 2

          // 定位
          $toolbar.css({
            top: top + 5,
            left: left,
            'margin-left': marginLeft
          })
          // 如果定位太靠左了
          if (marginLeft < 0) {
            // 得到三角形的margin-left
            $toolbar.css('margin-left', '0')
            $triangle.hide()
          } else {
            $triangle.show()
          }
        }

        // 隐藏 toolbar
        function hide() {
          if ($currentTable == null) {
            return
          }
          $currentTable.removeClass('clicked')
          $currentTable = null
          $toolbar.hide()
        }

        // click table 事件
        $currentTxt
          .on('click', 'table', function(e) {
            var $table = $(e.currentTarget)

            // 渲染
            render()

            if ($currentTable && $currentTable.get(0) === $table.get(0)) {
              setTimeout(hide, 100)
              return
            }

            // 显示 toolbar
            $currentTable = $table
            show()

            // 阻止冒泡
            e.preventDefault()
            e.stopPropagation()
          })
          .on('click keydown scroll', function(e) {
            setTimeout(hide, 100)
          })
        E.$body.on('click keydown scroll', function(e) {
          setTimeout(hide, 100)
        })
      })
    })
    // 编辑器区域 img toolbar
    _e(function(E, $) {
      if (E.userAgent.indexOf('MSIE 8') > 0) {
        return
      }

      E.plugin(function() {
        var editor = this
        var lang = editor.config.lang
        var txt = editor.txt
        var $txt = txt.$txt
        var html = ''
        // 说明：设置了 max-height 之后，$txt.parent() 负责滚动处理
        var $currentTxt = editor.useMaxHeight ? $txt.parent() : $txt
        var $editorContainer = editor.$editorContainer
        var $currentImg
        var currentLink = ''

        // 用到的dom节点
        var isRendered = false
        var $dragPoint = $('<div class="img-drag-point"></div>')

        var $toolbar = $('<div class="txt-toolbar"></div>')
        var $triangle = $('<div class="tip-triangle"></div>')

        var $menuContainer = $('<div></div>')
        var $delete = $(
          '<a href="#"><i class="wangeditor-menu-img-trash-o"></i></a>'
        )
        var $zoomSmall = $(
          '<a href="#"><i class="wangeditor-menu-img-search-minus"></i></a>'
        )
        var $zoomBig = $(
          '<a href="#"><i class="wangeditor-menu-img-search-plus"></i></a>'
        )
        // var $floatLeft = $('<a href="#"><i class="wangeditor-menu-img-align-left"></i></a>');
        // var $noFloat = $('<a href="#"><i class="wangeditor-menu-img-align-justify"></i></a>');
        // var $floatRight = $('<a href="#"><i class="wangeditor-menu-img-align-right"></i></a>');
        var $alignLeft = $(
          '<a href="#"><i class="wangeditor-menu-img-align-left"></i></a>'
        )
        var $alignCenter = $(
          '<a href="#"><i class="wangeditor-menu-img-align-center"></i></a>'
        )
        var $alignRight = $(
          '<a href="#"><i class="wangeditor-menu-img-align-right"></i></a>'
        )
        var $link = $(
          '<a href="#"><i class="wangeditor-menu-img-link"></i></a>'
        )
        var $unLink = $(
          '<a href="#"><i class="wangeditor-menu-img-unlink"></i></a>'
        )

        var $linkInputContainer = $('<div style="display:none;"></div>')
        var $linkInput = $(
          '<input type="text" style="height:26px; margin-left:10px; width:200px;"/>'
        )
        var $linkBtnSubmit = $(
          '<button class="right">' + lang.submit + '</button>'
        )
        var $linkBtnCancel = $(
          '<button class="right gray">' + lang.cancel + '</button>'
        )

        // 记录是否正在拖拽
        var isOnDrag = false

        // 获取 / 设置 链接
        function imgLink(e, url) {
          if (!$currentImg) {
            return
          }
          var commandFn
          var callback = function() {
            // 及时保存currentLink
            if (url != null) {
              currentLink = url
            }
            if (html !== $txt.html()) {
              $txt.change()
            }
          }
          var $link
          var inLink = false
          var $parent = $currentImg.parent()
          if ($parent.get(0).nodeName.toLowerCase() === 'a') {
            // 父元素就是图片链接
            $link = $parent
            inLink = true
          } else {
            // 父元素不是图片链接，则重新创建一个链接
            $link = $('<a target="_blank"></a>')
          }

          if (url == null) {
            // url 无值，是获取链接
            return $link.attr('href') || ''
          } else if (url === '') {
            // url 是空字符串，是取消链接
            if (inLink) {
              commandFn = function() {
                $currentImg.unwrap()
              }
            }
          } else {
            // url 有值，是设置链接
            if (url === currentLink) {
              return
            }
            commandFn = function() {
              $link.attr('href', url)

              if (!inLink) {
                // 当前图片未包含在链接中，则包含进来
                $currentImg.wrap($link)
              }
            }
          }

          // 执行命令
          if (commandFn) {
            // 记录下执行命令之前的html内容
            html = $txt.html()
            // 执行命令
            editor.customCommand(e, commandFn, callback)
          }
        }

        // 渲染到页面
        function render() {
          if (isRendered) {
            return
          }

          // 绑定事件
          bindToolbarEvent()
          bindDragEvent()

          // 菜单放入 container
          $menuContainer
            .append($delete)
            .append($zoomSmall)
            .append($zoomBig)
            // .append($floatLeft)
            // .append($noFloat)
            // .append($floatRight);
            .append($alignLeft)
            .append($alignCenter)
            .append($alignRight)
            .append($link)
            .append($unLink)

          // 链接input放入container
          $linkInputContainer
            .append($linkInput)
            .append($linkBtnCancel)
            .append($linkBtnSubmit)

          // 拼接 渲染到页面上
          $toolbar
            .append($triangle)
            .append($menuContainer)
            .append($linkInputContainer)

          editor.$editorContainer.append($toolbar).append($dragPoint)
          isRendered = true
        }

        // 绑定toolbar事件
        function bindToolbarEvent() {
          // 统一执行命令的方法
          var commandFn
          function customCommand(e, callback) {
            var cb
            // 记录下执行命令之前的html内容
            html = $txt.html()
            cb = function() {
              if (callback) {
                callback()
              }
              if (html !== $txt.html()) {
                $txt.change()
              }
            }
            // 执行命令
            if (commandFn) {
              editor.customCommand(e, commandFn, cb)
            }
          }

          // 删除
          $delete.click(function(e) {
            // 删除之前先unlink
            imgLink(e, '')

            // 删除图片
            commandFn = function() {
              $currentImg.remove()
            }
            customCommand(e, function() {
              setTimeout(hide, 100)
            })
          })

          // 放大
          $zoomBig.click(function(e) {
            commandFn = function() {
              var img = $currentImg.get(0)
              var width = img.width
              var height = img.height
              width = width * 1.1
              height = height * 1.1

              $currentImg.css({
                width: width + 'px',
                height: height + 'px'
              })
            }
            customCommand(e, function() {
              setTimeout(show)
            })
          })

          // 缩小
          $zoomSmall.click(function(e) {
            commandFn = function() {
              var img = $currentImg.get(0)
              var width = img.width
              var height = img.height
              width = width * 0.9
              height = height * 0.9

              $currentImg.css({
                width: width + 'px',
                height: height + 'px'
              })
            }
            customCommand(e, function() {
              setTimeout(show)
            })
          })

          // // 左浮动
          // $floatLeft.click(function (e) {
          //     commandFn = function () {
          //         $currentImg.css({
          //             float: 'left'
          //         });
          //     };
          //     customCommand(e, function () {
          //         setTimeout(hide, 100);
          //     });
          // });

          // alignLeft
          $alignLeft.click(function(e) {
            commandFn = function() {
              // 如果 img 增加了链接，那么 img.parent() 就是 a 标签，设置 align 没用的，因此必须找到 P 父节点来设置 align
              $currentImg
                .parents('p')
                .css({
                  'text-align': 'left'
                })
                .attr('align', 'left')
            }
            customCommand(e, function() {
              setTimeout(hide, 100)
            })
          })

          // // 右浮动
          // $floatRight.click(function (e) {
          //     commandFn = function () {
          //         $currentImg.css({
          //             float: 'right'
          //         });
          //     };
          //     customCommand(e, function () {
          //         setTimeout(hide, 100);
          //     });
          // });

          // alignRight
          $alignRight.click(function(e) {
            commandFn = function() {
              // 如果 img 增加了链接，那么 img.parent() 就是 a 标签，设置 align 没用的，因此必须找到 P 父节点来设置 align
              $currentImg
                .parents('p')
                .css({
                  'text-align': 'right'
                })
                .attr('align', 'right')
            }
            customCommand(e, function() {
              setTimeout(hide, 100)
            })
          })

          // // 无浮动
          // $noFloat.click(function (e) {
          //     commandFn = function () {
          //         $currentImg.css({
          //             float: 'none'
          //         });
          //     };
          //     customCommand(e, function () {
          //         setTimeout(hide, 100);
          //     });
          // });

          // alignCenter
          $alignCenter.click(function(e) {
            commandFn = function() {
              // 如果 img 增加了链接，那么 img.parent() 就是 a 标签，设置 align 没用的，因此必须找到 P 父节点来设置 align
              $currentImg
                .parents('p')
                .css({
                  'text-align': 'center'
                })
                .attr('align', 'center')
            }
            customCommand(e, function() {
              setTimeout(hide, 100)
            })
          })

          // link
          // 显示链接input
          $link.click(function(e) {
            e.preventDefault()

            // 获取当前链接，并显示
            currentLink = imgLink(e)
            $linkInput.val(currentLink)

            $menuContainer.hide()
            $linkInputContainer.show()
          })
          // 设置链接
          $linkBtnSubmit.click(function(e) {
            e.preventDefault()

            var url = $.trim($linkInput.val())
            if (url) {
              // 设置链接，同时会自动更新 currentLink 的值
              imgLink(e, url)
            }

            // 隐藏 toolbar
            setTimeout(hide)
          })
          // 取消设置链接
          $linkBtnCancel.click(function(e) {
            e.preventDefault()

            // 重置链接 input
            $linkInput.val(currentLink)

            $menuContainer.show()
            $linkInputContainer.hide()
          })

          // unlink
          $unLink.click(function(e) {
            e.preventDefault()

            // 执行 unlink
            imgLink(e, '')

            // 隐藏 toolbar
            setTimeout(hide)
          })
        }

        // 绑定drag事件
        function bindDragEvent() {
          var _x, _y
          var dragMarginLeft, dragMarginTop
          var imgWidth, imgHeight

          function mousemove(e) {
            var diffX, diffY

            // 计算差额
            diffX = e.pageX - _x
            diffY = e.pageY - _y

            // --------- 计算拖拽点的位置 ---------
            var currentDragMarginLeft = dragMarginLeft + diffX
            var currentDragMarginTop = dragMarginTop + diffY
            $dragPoint.css({
              'margin-left': currentDragMarginLeft,
              'margin-top': currentDragMarginTop
            })

            // --------- 计算图片的大小 ---------
            var currentImgWidth = imgWidth + diffX
            var currentImggHeight = imgHeight + diffY
            $currentImg &&
              $currentImg.css({
                width: currentImgWidth,
                height: currentImggHeight
              })
          }

          $dragPoint.on('mousedown', function(e) {
            if (!$currentImg) {
              return
            }
            // 当前鼠标位置
            _x = e.pageX
            _y = e.pageY

            // 当前拖拽点的位置
            dragMarginLeft = parseFloat($dragPoint.css('margin-left'), 10)
            dragMarginTop = parseFloat($dragPoint.css('margin-top'), 10)

            // 当前图片的大小
            imgWidth = $currentImg.width()
            imgHeight = $currentImg.height()

            // 隐藏 $toolbar
            $toolbar.hide()

            // 绑定计算事件
            E.$document.on('mousemove._dragResizeImg', mousemove)
            E.$document.on('mouseup._dragResizeImg', function(e) {
              // 取消绑定
              E.$document.off('mousemove._dragResizeImg')
              E.$document.off('mouseup._dragResizeImg')

              // 隐藏，并还原拖拽点的位置
              hide()
              $dragPoint.css({
                'margin-left': dragMarginLeft,
                'margin-top': dragMarginTop
              })

              // 记录
              isOnDrag = false
            })

            // 记录
            isOnDrag = true
          })
        }

        // 显示 toolbar
        function show() {
          if (editor._disabled) {
            // 编辑器已经被禁用，则不让显示
            return
          }
          if ($currentImg == null) {
            return
          }
          $currentImg.addClass('clicked')
          var imgPosition = $currentImg.position()
          var imgTop = imgPosition.top
          var imgLeft = imgPosition.left
          var imgHeight = $currentImg.outerHeight()
          var imgWidth = $currentImg.outerWidth()

          // --- 定位 dragpoint ---
          $dragPoint.css({
            top: imgTop + imgHeight,
            left: imgLeft + imgWidth
          })

          // --- 定位 toolbar ---

          // 计算初步结果
          var top = imgTop + imgHeight
          var left = imgLeft
          var marginLeft = 0

          var txtTop = $currentTxt.position().top
          var txtHeight = $currentTxt.outerHeight()
          if (top > txtTop + txtHeight) {
            // top 不得超出编辑范围
            top = txtTop + txtHeight
          } else {
            // top 超出编辑范围，dragPoint就不显示了
            $dragPoint.show()
          }

          // 显示（方便计算 margin）
          $toolbar.show()

          // 计算 margin
          var width = $toolbar.outerWidth()
          marginLeft = imgWidth / 2 - width / 2

          // 定位
          $toolbar.css({
            top: top + 5,
            left: left,
            'margin-left': marginLeft
          })
          // 如果定位太靠左了
          if (marginLeft < 0) {
            // 得到三角形的margin-left
            $toolbar.css('margin-left', '0')
            $triangle.hide()
          } else {
            $triangle.show()
          }

          // disable 菜单
          editor.disableMenusExcept()
        }

        // 隐藏 toolbar
        function hide() {
          if ($currentImg == null) {
            return
          }
          $currentImg.removeClass('clicked')
          $currentImg = null

          $toolbar.hide()
          $dragPoint.hide()

          // enable 菜单
          editor.enableMenusExcept()
        }

        // 判断img是否是一个表情
        function isEmotion(imgSrc) {
          var result = false
          if (!editor.emotionUrls) {
            return result
          }
          $.each(editor.emotionUrls, function(index, url) {
            var flag = false
            if (imgSrc === url) {
              result = true
              flag = true
            }
            if (flag) {
              return false // break 循环
            }
          })
          return result
        }

        // click img 事件
        $currentTxt
          .on('mousedown', 'img', function(e) {
            e.preventDefault()
          })
          .on('click', 'img', function(e) {
            var $img = $(e.currentTarget)
            var src = $img.attr('src')

            if (!src || isEmotion(src)) {
              // 是一个表情图标
              return
            }

            // ---------- 不是表情图标 ----------

            // 渲染
            render()

            if ($currentImg && $currentImg.get(0) === $img.get(0)) {
              setTimeout(hide, 100)
              return
            }

            // 显示 toolbar
            $currentImg = $img
            show()

            // 默认显示menuContainer，其他默认隐藏
            $menuContainer.show()
            $linkInputContainer.hide()

            // 阻止冒泡
            e.preventDefault()
            e.stopPropagation()
          })
          .on('click keydown scroll', function(e) {
            if (!isOnDrag) {
              setTimeout(hide, 100)
            }
          })
      })
    })
    // 编辑区域 link toolbar
    _e(function(E, $) {
      E.plugin(function() {
        var editor = this
        var lang = editor.config.lang
        var $txt = editor.txt.$txt

        // 当前命中的链接
        var $currentLink

        var $toolbar = $('<div class="txt-toolbar"></div>')
        var $triangle = $('<div class="tip-triangle"></div>')
        var $triggerLink = $(
          '<a href="#" target="_blank"><i class="wangeditor-menu-img-link"></i> ' +
            lang.openLink +
            '</a>'
        )
        var isRendered

        // 记录当前的显示/隐藏状态
        var isShow = false

        var showTimeoutId, hideTimeoutId
        var showTimeoutIdByToolbar, hideTimeoutIdByToolbar

        // 渲染 dom
        function render() {
          if (isRendered) {
            return
          }

          $toolbar.append($triangle).append($triggerLink)

          editor.$editorContainer.append($toolbar)

          isRendered = true
        }

        // 定位
        function setPosition() {
          if (!$currentLink) {
            return
          }

          var position = $currentLink.position()
          var left = position.left
          var top = position.top
          var height = $currentLink.height()

          // 初步计算top值
          var topResult = top + height + 5

          // 判断 toolbar 是否超过了编辑器区域的下边界
          var menuHeight = editor.menuContainer.height()
          var txtHeight = editor.txt.$txt.outerHeight()
          if (topResult > menuHeight + txtHeight) {
            topResult = menuHeight + txtHeight + 5
          }

          // 最终设置
          $toolbar.css({
            top: topResult,
            left: left
          })
        }

        // 显示 toolbar
        function show() {
          if (isShow) {
            return
          }

          if (!$currentLink) {
            return
          }

          render()

          $toolbar.show()

          // 设置链接
          var href = $currentLink.attr('href')
          $triggerLink.attr('href', href)

          // 定位
          setPosition()

          isShow = true
        }

        // 隐藏 toolbar
        function hide() {
          if (!isShow) {
            return
          }

          if (!$currentLink) {
            return
          }

          $toolbar.hide()
          isShow = false
        }

        // $txt 绑定事件
        $txt
          .on('mouseenter', 'a', function(e) {
            // 延时 500ms 显示toolbar
            if (showTimeoutId) {
              clearTimeout(showTimeoutId)
            }
            showTimeoutId = setTimeout(function() {
              var a = e.currentTarget
              var $a = $(a)
              $currentLink = $a

              var $img = $a.children('img')
              if ($img.length) {
                // 该链接下包含一个图片

                // 图片点击时，隐藏toolbar
                $img.click(function(e) {
                  hide()
                })

                if ($img.hasClass('clicked')) {
                  // 图片还处于clicked状态，则不显示toolbar
                  return
                }
              }

              // 显示toolbar
              show()
            }, 500)
          })
          .on('mouseleave', 'a', function(e) {
            // 延时 500ms 隐藏toolbar
            if (hideTimeoutId) {
              clearTimeout(hideTimeoutId)
            }
            hideTimeoutId = setTimeout(hide, 500)
          })
          .on('click keydown scroll', function(e) {
            setTimeout(hide, 100)
          })
        // $toolbar 绑定事件
        $toolbar
          .on('mouseenter', function(e) {
            // 先中断掉 $txt.mouseleave 导致的隐藏
            if (hideTimeoutId) {
              clearTimeout(hideTimeoutId)
            }
          })
          .on('mouseleave', function(e) {
            // 延时 500ms 显示toolbar
            if (showTimeoutIdByToolbar) {
              clearTimeout(showTimeoutIdByToolbar)
            }
            showTimeoutIdByToolbar = setTimeout(hide, 500)
          })
      })
    })
    // menu吸顶
    _e(function(E, $) {
      E.plugin(function() {
        var editor = this
        var menuFixed = editor.config.menuFixed
        if (menuFixed === false || typeof menuFixed !== 'number') {
          // 没有配置菜单吸顶
          return
        }
        var bodyMarginTop = parseFloat(E.$body.css('margin-top'), 10)
        if (isNaN(bodyMarginTop)) {
          bodyMarginTop = 0
        }

        var $editorContainer = editor.$editorContainer
        var editorTop = $editorContainer.offset().top
        var editorHeight = $editorContainer.outerHeight()

        var $menuContainer = editor.menuContainer.$menuContainer
        var menuCssPosition = $menuContainer.css('position')
        var menuCssTop = $menuContainer.css('top')
        var menuTop = $menuContainer.offset().top
        var menuHeight = $menuContainer.outerHeight()

        var $txt = editor.txt.$txt

        E.$window.scroll(function() {
          //全屏模式不支持
          if (editor.isFullScreen) {
            return
          }

          var sTop = E.$window.scrollTop()

          // 需要重新计算宽度，因为浏览器可能此时出现滚动条
          var menuWidth = $menuContainer.width()

          // 如果 menuTop === 0 说明此前编辑器一直隐藏，后来显示出来了，要重新计算相关数据
          if (menuTop === 0) {
            menuTop = $menuContainer.offset().top
            editorTop = $editorContainer.offset().top
            editorHeight = $editorContainer.outerHeight()
            menuHeight = $menuContainer.outerHeight()
          }

          if (
            sTop >= menuTop &&
            sTop + menuFixed + menuHeight + 30 < editorTop + editorHeight
          ) {
            // 吸顶
            $menuContainer.css({
              position: 'fixed',
              top: menuFixed
            })

            // 固定宽度
            $menuContainer.width(menuWidth)

            // 增加body margin-top
            E.$body.css({
              'margin-top': bodyMarginTop + menuHeight
            })

            // 记录
            if (!editor._isMenufixed) {
              editor._isMenufixed = true
            }
          } else {
            // 取消吸顶
            $menuContainer.css({
              position: menuCssPosition,
              top: menuCssTop
            })

            // 取消宽度固定
            $menuContainer.css('width', '100%')

            // 还原 body margin-top
            E.$body.css({
              'margin-top': bodyMarginTop
            })

            // 撤销记录
            if (editor._isMenufixed) {
              editor._isMenufixed = false
            }
          }
        })
      })
    })
    // 缩进 菜单插件
    _e(function(E, $) {
      // 用 createMenu 方法创建菜单
      E.createMenu(function(check) {
        // 定义菜单id，不要和其他菜单id重复。编辑器自带的所有菜单id，可通过『参数配置-自定义菜单』一节查看
        var menuId = 'indent'

        // check将检查菜单配置（『参数配置-自定义菜单』一节描述）中是否该菜单id，如果没有，则忽略下面的代码。
        if (!check(menuId)) {
          return
        }

        // this 指向 editor 对象自身
        var editor = this

        // 创建 menu 对象
        var menu = new E.Menu({
          editor: editor, // 编辑器对象
          id: menuId, // 菜单id
          title: '缩进', // 菜单标题

          // 正常状态和选中装下的dom对象，样式需要自定义
          $domNormal: $(
            '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-indent-left"></i></a>'
          ),
          $domSelected: $(
            '<a href="#" tabindex="-1" class="selected"><i class="wangeditor-menu-img-indent-left"></i></a>'
          )
        })

        // 菜单正常状态下，点击将触发该事件
        menu.clickEvent = function(e) {
          var elem = editor.getRangeElem()
          var p = editor.getSelfOrParentByName(elem, 'p')
          var $p

          if (!p) {
            // 未找到 p 元素，则忽略
            return e.preventDefault()
          }
          $p = $(p)

          // 使用自定义命令
          function commandFn() {
            $p.css('text-indent', '2em')
          }
          editor.customCommand(e, commandFn)
        }

        // 菜单选中状态下，点击将触发该事件
        menu.clickEventSelected = function(e) {
          var elem = editor.getRangeElem()
          var p = editor.getSelfOrParentByName(elem, 'p')
          var $p

          if (!p) {
            // 未找到 p 元素，则忽略
            return e.preventDefault()
          }
          $p = $(p)

          // 使用自定义命令
          function commandFn() {
            $p.css('text-indent', '0')
          }
          editor.customCommand(e, commandFn)
        }

        // 根据当前选区，自定义更新菜单的选中状态或者正常状态
        menu.updateSelectedEvent = function() {
          // 获取当前选区所在的父元素
          var elem = editor.getRangeElem()
          var p = editor.getSelfOrParentByName(elem, 'p')
          var $p
          var indent

          if (!p) {
            // 未找到 p 元素，则标记为未处于选中状态
            return false
          }
          $p = $(p)
          indent = $p.css('text-indent')

          if (!indent || indent === '0px') {
            // 得到的p，text-indent 属性是 0，则标记为未处于选中状态
            return false
          }

          // 找到 p 元素，并且 text-indent 不是 0，则标记为选中状态
          return true
        }

        // 增加到editor对象中
        editor.menus[menuId] = menu
      })
    })
    // 行高 菜单插件
    _e(function(E, $) {
      // 用 createMenu 方法创建菜单
      E.createMenu(function(check) {
        // 定义菜单id，不要和其他菜单id重复。编辑器自带的所有菜单id，可通过『参数配置-自定义菜单』一节查看
        var menuId = 'lineheight'

        // check将检查菜单配置（『参数配置-自定义菜单』一节描述）中是否该菜单id，如果没有，则忽略下面的代码。
        if (!check(menuId)) {
          return
        }

        // this 指向 editor 对象自身
        var editor = this

        // 由于浏览器自身不支持 lineHeight 命令，因此要做一个hook
        editor.commandHooks.lineHeight = function(value) {
          var rangeElem = editor.getRangeElem()
          var targetElem = editor.getSelfOrParentByName(
            rangeElem,
            'p,h1,h2,h3,h4,h5,pre'
          )
          if (!targetElem) {
            return
          }
          $(targetElem).css('line-height', value + '')
        }

        // 创建 menu 对象
        var menu = new E.Menu({
          editor: editor, // 编辑器对象
          id: menuId, // 菜单id
          title: '行高', // 菜单标题
          commandName: 'lineHeight', // 命令名称

          // 正常状态和选中装下的dom对象，样式需要自定义
          $domNormal: $(
            '<a href="#" tabindex="-1"><i class="wangeditor-menu-img-arrows-v"></i></a>'
          ),
          $domSelected: $(
            '<a href="#" tabindex="-1" class="selected"><i class="wangeditor-menu-img-arrows-v"></i></a>'
          )
        })

        // 数据源
        var data = {
          // 格式： 'value' : 'title'
          '1.0': '1.0倍',
          '1.5': '1.5倍',
          '1.8': '1.8倍',
          '2.0': '2.0倍',
          '2.5': '2.5倍',
          '3.0': '3.0倍'
        }

        // 为menu创建droplist对象
        var tpl = '<span style="line-height:{#commandValue}">{#title}</span>'
        menu.dropList = new E.DropList(editor, menu, {
          data: data, // 传入数据源
          tpl: tpl // 传入模板
        })

        // 增加到editor对象中
        editor.menus[menuId] = menu
      })
    })
    // 自定义上传
    _e(function(E, $) {
      E.plugin(function() {
        var editor = this
        var customUpload = editor.config.customUpload
        if (!customUpload) {
          return
        } else if (editor.config.uploadImgUrl) {
          alert('自定义上传无效，详看浏览器日志console.log')
          E.error(
            '已经配置了 uploadImgUrl ，就不能再配置 customUpload ，两者冲突。将导致自定义上传无效。'
          )
          return
        }

        var $uploadContent = editor.$uploadContent
        if (!$uploadContent) {
          E.error('自定义上传，无法获取 editor.$uploadContent')
        }

        // UI
        var $uploadIcon = $(
          '<div class="upload-icon-container"><i class="wangeditor-menu-img-upload"></i></div>'
        )
        $uploadContent.append($uploadIcon)

        // 设置id，并暴露
        var btnId = 'upload' + E.random()
        var containerId = 'upload' + E.random()
        $uploadIcon.attr('id', btnId)
        $uploadContent.attr('id', containerId)

        editor.customUploadBtnId = btnId
        editor.customUploadContainerId = containerId
      })
    })
    // 版权提示
    _e(function(E, $) {
      E.info(
        '本页面富文本编辑器由 wangEditor 提供 http://wangeditor.github.io/ '
      )
    })
  }
  // 最终返回wangEditor构造函数
  return E
})
